实现线程安全双向关联的好方法是什么?是否有一个好的库或代码生成器?
这是一个非线程安全的例子:
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.
发布评论
评论(6)
Google Guava 会为您执行此操作:BiMap。
例如:
someMutexObject
可以是您想要同步
的任何对象。Google Guava does this for you: BiMap.
For example:
someMutexObject
can be any object you would want tosynchronize
on.您可以将每个对象关联到它们自己的锁,然后在获取两个锁时设置另一个对象。例如。为了避免死锁,您可以使用锁排序
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
试试这个,将允许在不进行写入的情况下进行读取。
可重入ReadWriteLock
Try this, will allow reading while no writing is done.
ReentrantReadWriteLock
另一种选择是简单地使
other
引用变得易失。这将满足您的要求和您的必备品。The other alternative is to simply make the
other
reference(s) volatile. 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.
事实证明这是一个非常困难的问题! (很好!)使用全局锁太容易了,而且可能太慢。我想我有一个无锁版本——我将在下面介绍——但我不会太相信它是完美的。很难推理出所有可能的交错。
事实证明,这是事务内存的完美用例!只需将整个块标记为原子并修改您想要的任何内容!您可以查看 Deuce STM,尽管我不知道它有多快。如果最好的系统不需要定制硬件就好了……
无论如何,在思考这个问题一段时间后,我想我想出了一个使用 Java 的 AtomicReference。首先是代码:
要点:
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:
Key points:
x.oRef.compareAndSet(y, null)
.f.oRef.compareAndSet(null, f)
succeeds, no other thread will be able to break the half-established relationship inbreak()
. Then ifoRef.compareAndSet(null, f)
succeeds, the operation is complete. If it fails,f.oRef
can be reset and everyone retries.