[英]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: 我对线程安全的要求是:
assert foo.getOther().getOther() == foo
fails when another thread is performing setOther
concurrently. setOther
时, assert foo.getOther().getOther() == foo
是可以接受的。 setOther
and no other other thread overrides the value, getOther
immediately returns the new value for that thread. setOther
而没有其他其他线程覆盖该值,则getOther
立即返回该线程的新值。 getOther
, it will never again receive the old value (unless it is set again). getOther
观察到新值,它将永远不会再次接收旧值(除非再次设置)。 Also nice to have: 也很高兴有:
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. 试试这个,在没有写完的情况下允许阅读。
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: 关键点:
x.oRef.compareAndSet(y, null)
. x.oRef.compareAndSet(y, null)
。 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.