简体   繁体   English

Java中的Threadsafe双向关联

[英]Threadsafe bidirectional association in Java

What is a good way to implement thread-safe bidirectional associations? 实现线程安全的双向关联有什么好方法? Is there maybe a good library or code generator? 是否有一个好的库或代码生成器?

Here is a non thread-safe example: 这是一个非线程安全的示例:

class Foo {

    private Foo other;

    public Foo getOther() {
        return other;
    }

    public void setOther(Foo other) {
        this.setOtherSecretly(other);
        other.setotherSecretly(this);
    }

    void setOtherSecretly(Foo other) {
        if (this.other != null) this.other.other = null;
        this.other = other;
    }
}

My requirements for thread-safety are: 我对线程安全的要求是:

  • No deadlocks 没有死锁
  • Eventual consistency (When all threads stop modifying the objects, a consistent state is eventually reached. Ie, it is acceptable that assert foo.getOther().getOther() == foo fails when another thread is performing setOther concurrently. 最终的一致性(当所有线程停止修改对象时,最终会达到一致状态。即,当另一个线程同时执行setOther时, assert foo.getOther().getOther() == foo是可以接受的。
  • Sequential behaviour. 顺序行为。 If a thread performs setOther and no other other thread overrides the value, getOther immediately returns the new value for that thread. 如果线程执行setOther而没有其他其他线程覆盖该值,则getOther立即返回该线程的新值。
  • No traveling back in time. 没有时光倒流。 Once a thread observed a new value with getOther , it will never again receive the old value (unless it is set again). 一旦线程使用getOther观察到新值,它将永远不会再次接收旧值(除非再次设置)。

Also nice to have: 也很高兴有:

  • Low contention, especially no global lock. 低争用,尤其是没有全局锁定。 The solution should scale well. 解决方案应该很好地扩展。
  • As little synchronization overhead as possible. 尽可能少的同步开销。 It should have reasonable performance for a single thread. 对于单个线程,它应该具有合理的性能。
  • Low memory overhead. 内存开销低。 When an object has 5 associations, I don't want 3 additional fields per association. 当一个对象有5个关联时,我不希望每个关联有3个额外的字段。 Local variables in setters are ok. setter中的局部变量是可以的。

My application will have 16 threads working on about 5.000 objects of several classes. 我的应用程序将有16个线程处理大约5.000个几个类的对象。

I couldn't come up with a solution yet (no, this is not homework), so any input (ideas, articles, code) is welcome. 我还没有想出一个解决方案(不,这不是功课),所以任何输入(想法,文章,代码)都是受欢迎的。

Google Guava does this for you: BiMap . Google Guava为您做到这一点: BiMap

For example: 例如:

BiMap<Integer, String> bimap = Synchronized.biMap(HashBiMap.create(), someMutexObject);
bimap.put(1, "one");
bimap.put(2, "two");

bimap.get(1); // returns "one"
bimap.inverse().get("one") // returns 1

someMutexObject can be any object you would want to synchronize on. someMutexObject可以是您想要synchronize任何对象。

You can associate each object to their own lock and then set the other while acquiring both locks. 您可以将每个对象关联到自己的锁定,然后在获取两个锁定时设置另一个对象。 For instance. 例如。 To avoid deadlock you can use lock ordering 为避免死锁,您可以使用锁定顺序

class Foo extends ReentrantLock {

    private static final AtomicInteger order = new AtomicInteger(0);

    final int id = order.incrementAndGet();

    private Foo other;

    public Foo getOther() {
        return other;
    }

    public void setOther(Foo other) {
        if (id > other.id) {
            other.lock();
            try {
                this.lock();
                try {

                    // assign here
                } finally {
                    this.unlock();
                }
            } finally {
                other.unlock();
            }
        } else if (id < other.id) {
            this.lock();
            try {
                other.lock();
                try {

                    // assign here
                } finally {
                    other.unlock();
                }
            } finally {
                this.unlock();
            }
        }
    }
}

Try this, will allow reading while no writing is done. 试试这个,在没有写完的情况下允许阅读。

ReentrantReadWriteLock 的ReentrantReadWriteLock

The other alternative is to simply make the other reference(s) volatile. 另一种选择是简单地使other参考不稳定。 That will meet your requirement and your nice-to-haves. 这将满足您的要求和您的好处。

I can think of an static member to work as a monitor. 我可以想到一个静态成员作为一个监视器。 but maybe this is what you consider 'global' lock. 但也许这就是你认为'全球'的锁定。

class Foo {

    private static final Object MONITOR = new Object();
    private Foo other;

    public Foo getOther() {
        synchronized(MONITOR){
            return other;
        }
    }

    public void setOther(Foo other) {
        synchronized(MONITOR){
            this.setOtherSecretly(other);
            other.setotherSecretly(this);
        }
    }

    void setOtherSecretly(Foo other) {
        if (this.other != null) this.other.other = null;
        this.other = other;
    }
}

This turns out to be a really hard problem! 事实证明这是一个非常难的问题! (Nice!) Using a global lock would be too easy, and probably too slow. (很好!)使用全局锁定太简单了,而且可能太慢了。 I think I have a lock-free version--which I'll get into below--but I wouldn't put too much faith in it being perfect. 我想我有一个无锁版本 - 我将在下面介绍 - 但我不会过分相信它是完美的。 It's hard to reason about all the possible interleavings. 所有可能的交错都很难推理。

As it turns out, this is a perfect use case for transactional memory ! 事实证明,这是事务性内存的完美用例! Just mark the whole block as atomic and modify whatever you want! 只需将整个块标记为原子并修改您想要的任何内容! You might look at Deuce STM , though I don't know how fast it might be. 你可能会看看Deuce STM ,虽然我不知道它有多快。 If only the best systems didn't need custom hardware... 如果只有最好的系统不需要定制硬件......

Anyway, after thinking through this problem for a while, I think I came up with a version that bypasses locks using Java's AtomicReference . 无论如何,在思考了这个问题一段时间之后,我想我想出了一个使用Java的AtomicReference来绕过锁的版本。 First, the code: 一,代码:

class Foo {

    private AtomicReference<Foo> oRef = new AtomicReference<Foo>;

    private static final AtomicInteger order = new AtomicInteger(0);
    private final int id = order.incrementAndGet();

    private static bool break(Foo x, Foo y) {
        if (x.id > y.id)
            return break(y, x);

        return x.oRef.compareAndSet(y, null) &&
               y.oRef.compareAndSet(x, null);
    }

    public void setOther(Foo f) {
        if (f != null && f.id > id) {
            f.setOther(this);
            return;
        }
        do {
            Foo other = oRef.get();
            if (other == f)
                break;

            if (other != null && !break(this, other))
                continue;

            if (f == null)
                break;

            Foo fother = f.oRef.get();
            if (fother != null && !break(f, fother))
                continue;

            if (!f.oRef.compareAndSet(null, this))
                continue;
            if (!oRef.compareAndSet(null, f)) {
                f.oRef.set(null);
                continue;
            }
        } while (false);
    }
}

Key points: 关键点:

  • If there are no concurrent accesses to any of the affected Foos (at most 4), the setter makes one pass through the loop modifying the relevant pointers. 如果没有任何并发​​访问任何受影响的Foos(最多4个),则setter会通过循环修改相关指针。
  • In the presence of concurrent setters, some of the setters might fail and retry. 在存在并发setter的情况下,某些setter可能会失败并重试。
  • If multiple threads try to break a relationship concurrently, only one thread will succeed executing x.oRef.compareAndSet(y, null) . 如果多个线程试图同时断开关系,则只有一个线程将成功执行x.oRef.compareAndSet(y, null)
  • If f.oRef.compareAndSet(null, f) succeeds, no other thread will be able to break the half-established relationship in break() . 如果f.oRef.compareAndSet(null, f)成功,则没有其他线程能够破坏break()的半成熟关系。 Then if oRef.compareAndSet(null, f) succeeds, the operation is complete. 然后,如果oRef.compareAndSet(null, f)成功,则操作完成。 If it fails, f.oRef can be reset and everyone retries. 如果失败,可以重置f.oRef并重试每个人。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM