Java中线程安全的双向关联

发布于 2024-10-18 05:35:12 字数 990 浏览 9 评论 0 原文

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

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

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

我对线程安全的要求是:

  • 无死锁
  • 最终一致性(当所有线程停止修改对象时,最终达到一致状态。即,可以接受 assert foo.getOther当另一个线程同时执行 setOther 时,
  • 如果一个线程执行 setOther 并且没有其他线程覆盖,则 立即返回该线程的新值。
  • ().getOther() == foo 失败。一旦线程使用 getOther 观察到新值,getOther 就会 值(除非再次设置)。

还有一个好处:

  • 低争用,尤其是没有全局锁时,它
  • 具有尽可能小的同步开销。
  • 应该 一个对象有 5 个关联,我不希望每个关联有 3 个附加字段。 setter 中的局部变量是可以的。

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

我还无法想出解决方案(不,这不是家庭作业),所以欢迎任何输入(想法、文章、代码)。

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. I.e., it is acceptable that assert foo.getOther().getOther() == foo fails when another thread is performing setOther concurrently.
  • Sequential behaviour. If a thread performs setOther and no other other thread overrides the value, getOther immediately returns the new value for that thread.
  • 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).

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. Local variables in setters are ok.

My application will have 16 threads working on about 5.000 objects of several classes.

I couldn't come up with a solution yet (no, this is not homework), so any input (ideas, articles, code) is welcome.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

秋叶绚丽 2024-10-25 05:35:12

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

Google Guava does this for you: 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.

木槿暧夏七纪年 2024-10-25 05:35:12

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

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

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();
            }
        }
    }
}
完美的未来在梦里 2024-10-25 05:35:12

试试这个,将允许在不进行写入的情况下进行读取。

可重入ReadWriteLock

Try this, will allow reading while no writing is done.

ReentrantReadWriteLock

女中豪杰 2024-10-25 05:35:12

另一种选择是简单地使other引用变得易失。这将满足您的要求和您的必备品。

The other alternative is to simply make the other reference(s) volatile. That will meet your requirement and your nice-to-haves.

み青杉依旧 2024-10-25 05:35:12

我可以想到一个静态成员来充当监视器。但也许这就是您所认为的“全局”锁。

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

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;
    }
}
信仰 2024-10-25 05:35:12

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

事实证明,这是事务内存的完美用例!只需将整个块标记为原子并修改您想要的任何内容!您可以查看 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);
    }
}

要点:

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

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. 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. 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.
  • In the presence of concurrent setters, some of the setters might fail and retry.
  • If multiple threads try to break a relationship concurrently, only one thread will succeed executing 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(). Then if oRef.compareAndSet(null, f) succeeds, the operation is complete. If it fails, f.oRef can be reset and everyone retries.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文