当从多个线程以相反顺序执行equals()时,Java的同步集合出现问题
示例场景:当从多个线程以相反顺序执行equals()时,Java的同步集合出现问题,java,collections,deadlock,Java,Collections,Deadlock,示例场景: 创建两个同步集(s1和s2) 将它们传递给两个线程(T1和T2) 启动线程 T1的run(): 而(永远) s1.等于(s2) T2的run() 而(永远) s2.等于(s1) 会发生什么? -SynchronizedSet的equals获得自身的锁 它计算传入的参数的长度以及它包含的内容,以确定其是否相等[注意:这是基于我分析的日志的猜测] 如果传入的参数也是SynchronizedSet,那么对size()和containAll()的调用也意味着必须获取该参数的锁 在上例中
- 创建两个同步集(s1和s2)
- 将它们传递给两个线程(T1和T2)
- 启动线程
- 它计算传入的参数的长度以及它包含的内容,以确定其是否相等[注意:这是基于我分析的日志的猜测]
- 如果传入的参数也是SynchronizedSet,那么对size()和containAll()的调用也意味着必须获取该参数的锁
- 在上例中,T1和T2的锁获取顺序如下: T1:s1->s2 T2:s2->s1
我认为这是一个JavaAPI限制(设计)。如何克服这个问题?如何确保我的应用程序中不会发生这种情况?在不陷入这种情况的情况下,我是否应该遵循一些设计原则?您可以在每个线程中以相同的顺序锁定两个集合:
synchronized(s1) {
synchronized(s2) {
s1.equals(s2);
}
}
及
我可能建议使用synchronized(){]块 大概是这样的:
while(forever){
synchronized(s1){
s1.equals(s2);
}
}
及
我认为这是一个JavaAPI限制(设计)
我相信你错了。我曾经使用过的每个PL级锁定方案的一个基本要求是线程必须以相同的顺序锁定资源,否则会出现死锁。这同样适用于数据库
事实上,我认为你能避免这种情况的唯一方法是:
- 要求应用程序获取单个原子操作中所需的所有锁,或
- 使用单个全局锁执行所有锁定
编写应用程序代码,使所有线程以相同的顺序获得锁。@Maurice和@Nettogrof的答案给出了如何实现这一点的示例,但如果需要担心大量集合,则可能会更困难。Stephen C的方法很好。进一步信息:如果不知道集合的方向,可以使用无论何时比较两个集合,都会出现“全局”锁定:
private static final Object lock = new Object(); // May be context-local.
[...]
synchronized (lock) {
synchronized (s1) {
synchronized (s2) {
return s1.equals(s2);
}
}
}
如果这些集合可能会被争用,您可以在大多数情况下按标识哈希代码排序,并返回到全局锁:
int h1 = System.identityHashCode(s1);
int h2 = System.identityHashCode(s2);
return
h1<h2 ? lockFirstEquals(h1, h2) :
h2<h1 ? lockFirstEquals(h2, h1) :
globalLockEquals(h1, h2);
最好的解决方案可能是更改线程策略,这样您就不需要在此处进行任何锁定。在执行
equals()之前,您可以按它们的顺序对集合进行排序
调用。这样,锁获取的顺序将始终保持不变。@Tom:但请注意,如果不同时获取辅助锁,您仍然可能会遇到主锁的问题。例如,如果某个已经持有s2
锁的线程执行第一个代码sni,则可能会出现死锁事实上,我认为你的第一个代码片段没有抓住我的重点。我想说的是,保证没有死锁的方法之一是对所有数据结构使用一个且只有一个锁。当然,这是不切实际的,因为(首先)它不可伸缩。如果一个线程已经持有锁并调用随机代码,它无论如何都会遇到麻烦。我想如果你使用java.util.concurrent.locks
,你可以对它进行编码,但我会尝试编写更合理的代码。@Tom的解决方案暗示了你的解决方案的问题。有一个很小但有限的概率至少,这两个集合将具有相同的标识哈希代码值。由于您实际获得的哈希值集合有限,“生日悖论”,在一次运行中多次执行该操作并在多台机器上运行,因此它实际上会经常发生。尽管您可能没有注意到,但并非所有潜在的死锁事件都会死锁。
private static final Object lock = new Object(); // May be context-local.
[...]
synchronized (lock) {
synchronized (s1) {
synchronized (s2) {
return s1.equals(s2);
}
}
}
int h1 = System.identityHashCode(s1);
int h2 = System.identityHashCode(s2);
return
h1<h2 ? lockFirstEquals(h1, h2) :
h2<h1 ? lockFirstEquals(h2, h1) :
globalLockEquals(h1, h2);
public StringBuffer append(StringBuffer other) {
if (other == null) {
return append("null");
}
int thisHash = System.identityHashCode(this);
int otherHash = System.identityHashCode(other);
if (thisHash < otherHash) {
synchronized (this) {
synchronized (other) {
appendImpl(other);
}
}
} else if (otherHash < thisHash) {
synchronized (other) {
synchronized (this) {
appendImpl(other);
}
}
} else {
append(other.toString()); // Or append((Object)other);
}
return this;
}