繁体   English   中英

Java中的Threadsafe双向关联

[英]Threadsafe bidirectional association in Java

实现线程安全的双向关联有什么好方法? 是否有一个好的库或代码生成器?

这是一个非线程安全的示例:

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;
    }
}

我对线程安全的要求是:

  • 没有死锁
  • 最终的一致性(当所有线程停止修改对象时,最终会达到一致状态。即,当另一个线程同时执行setOther时, assert foo.getOther().getOther() == foo是可以接受的。
  • 顺序行为。 如果线程执行setOther而没有其他其他线程覆盖该值,则getOther立即返回该线程的新值。
  • 没有时光倒流。 一旦线程使用getOther观察到新值,它将永远不会再次接收旧值(除非再次设置)。

也很高兴有:

  • 低争用,尤其是没有全局锁定。 解决方案应该很好地扩展。
  • 尽可能少的同步开销。 对于单个线程,它应该具有合理的性能。
  • 内存开销低。 当一个对象有5个关联时,我不希望每个关联有3个额外的字段。 setter中的局部变量是可以的。

我的应用程序将有16个线程处理大约5.000个几个类的对象。

我还没有想出一个解决方案(不,这不是功课),所以任何输入(想法,文章,代码)都是受欢迎的。

Google Guava为您做到这一点: BiMap

例如:

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可以是您想要synchronize任何对象。

您可以将每个对象关联到自己的锁定,然后在获取两个锁定时设置另一个对象。 例如。 为避免死锁,您可以使用锁定顺序

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();
            }
        }
    }
}

试试这个,在没有写完的情况下允许阅读。

的ReentrantReadWriteLock

另一种选择是简单地使other参考不稳定。 这将满足您的要求和您的好处。

我可以想到一个静态成员作为一个监视器。 但也许这就是你认为'全球'的锁定。

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;
    }
}

事实证明这是一个非常难的问题! (很好!)使用全局锁定太简单了,而且可能太慢了。 我想我有一个无锁版本 - 我将在下面介绍 - 但我不会过分相信它是完美的。 所有可能的交错都很难推理。

事实证明,这是事务性内存的完美用例! 只需将整个块标记为原子并修改您想要的任何内容! 你可能会看看Deuce STM ,虽然我不知道它有多快。 如果只有最好的系统不需要定制硬件......

无论如何,在思考了这个问题一段时间之后,我想我想出了一个使用Java的AtomicReference来绕过锁的版本。 一,代码:

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);
    }
}

关键点:

  • 如果没有任何并发​​访问任何受影响的Foos(最多4个),则setter会通过循环修改相关指针。
  • 在存在并发setter的情况下,某些setter可能会失败并重试。
  • 如果多个线程试图同时断开关系,则只有一个线程将成功执行x.oRef.compareAndSet(y, null)
  • 如果f.oRef.compareAndSet(null, f)成功,则没有其他线程能够破坏break()的半成熟关系。 然后,如果oRef.compareAndSet(null, f)成功,则操作完成。 如果失败,可以重置f.oRef并重试每个人。

暂无
暂无

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

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