实现线程安全双向关联的好方法是什么?可能有一个好的库或代码生成器吗?
这是一个非线程安全的例子:
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().getOther() == foo
时setOther
失败是可以接受的。setOther
而其他任何线程没有覆盖该值,getOther
会立即返回该线程的新值。getOther
观察到新值,它将永远不会再次接收旧值(除非再次设置)。也很高兴:
我的应用程序将有16个线程处理大约5.000个几个类的对象。
我还没有提出解决方案(不,这不是作业),所以欢迎任何输入(想法,文章,代码)。
答案 0 :(得分:2)
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
开启的任何对象。
答案 1 :(得分:1)
您可以将每个对象关联到自己的锁定,然后在获取两个锁定时设置另一个对象。例如。为避免死锁,您可以使用锁定顺序
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();
}
}
}
}
答案 2 :(得分:0)
试试这个,在没有写作的情况下允许阅读。
答案 3 :(得分:0)
另一种选择是简单地使other
引用变得不稳定。这将满足您的要求和您的好处。
答案 4 :(得分:0)
我可以将静态成员视为监视器。但也许这就是你认为的“全球”锁定。
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;
}
}
答案 5 :(得分:0)
事实证明这是一个非常难的问题! (很好!)使用全局锁定太简单了,而且可能太慢了。我想我有一个无锁版本 - 我将在下面介绍 - 但我不会过分相信它是完美的。很难推断所有可能的交错。
事实证明,这是transactional memory的完美用例!只需将整个块标记为原子并修改您想要的任何内容!您可能会看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);
}
}
关键点:
x.oRef.compareAndSet(y, null)
。f.oRef.compareAndSet(null, f)
成功,其他任何线程都无法打破break()
中半成熟的关系。然后,如果oRef.compareAndSet(null, f)
成功,则操作完成。如果失败,则可以重置f.oRef
,并且每个人都会重试。