我有以下代码: 注意:为了便于阅读,我尽可能地简化了代码。 如果我忘了任何关键的部分,请告诉我。
public class User(){
private Relations relations;
public User(){
relations = new Relations(this);
}
public getRelations(){
return relations;
}
}
public class Relations(){
private User user;
public Relations(User user){
this.user = user;
}
public synchronized void setRelation(User user2){
Relations relations2 = user2.getRelations();
synchronized(relations2){
storeRelation(user2);
if(!relations2.hasRelation(user))
relations2.setRelation(user);
}
}
public synchronized boolean hasRelation(User user2){
... // Checks if this relation is present in some kind of collection
}
/*Store this relation, unless it is already present*/
private void storeRelation(User user2){
... // Stores this relation in some kind of collection
}
}
此实现应确保所有关系x,y与:
x.user = u_x
y.user = u_y
以下不变量持有:
x.hasRelation( u_y ) <=> y.hasRelation( u_x )
我认为上述代码适用吗?
注意:在执行setRelation(..)时,它当然不成立, 但在那一刻,所涉及的两个关系的锁定都是 由执行线程持有,所以没有其他线程可以读取 hasRelation(..)涉及的其中一个关系。
假设这种情况持续下去,我相信仍存在潜在的死锁风险。 那是对的吗?如果是,我该如何解决? 我想我需要以某种方式原子地获取setRelation(..)中的两个锁。
答案 0 :(得分:8)
你在两点上都是正确的:你的不变量确实成立(假设我正确理解你的方法名称是什么意思等等,并且假设if(!relations.hasRelation(user)) relations2.setRelation(user2);
你打算写if(!relations2.hasRelation(user)) relations2.setRelation(user);
),但你确实存在死锁的风险:如果一个线程需要在x
然后在y
上获得锁定,而另一个线程需要在y
上获得锁定然后再开启x
,那么每个线程都有成功获取第一个锁的风险,从而阻止其他线程获得第二个锁。
一种解决方案是强制执行严格的通用排序以获取Relations
实例上的锁定。你要做的是,你添加一个常数整数字段lockOrder
:
private final int lockOrder;
和一个静态整数字段currentLockOrder
:
private static int currentLockOrder = 0;
每次创建Relations
实例时,都会将其lockOrder
设置为currentLockOrder
的当前值,并递增说:
public Relations()
{
synchronized(Relations.class) // a lock on currentLockOrder
{
lockOrder = currentLockOrder;
++currentLockOrder;
}
}
这样Relations
的每个实例都会为lockOrder
提供一个不同的,不可变的值。然后,您的setRelation
方法将按指定的顺序获取锁:
public void setRelation(final User thatUser)
{
final Relations that = thatUser.getRelations();
synchronized(lockOrder < that.lockOrder ? this : that)
{
synchronized(lockOrder < that.lockOrder ? that : this)
{
storeRelation(thatUser);
if(! that.hasRelation(user))
that.storeRelation(user);
}
}
}
从而确保如果两个线程都需要在x
和y
上获取锁定,那么它们都将首先获得x
上的锁定,或者它们都将首先获得锁定获取y
上的锁定。无论哪种方式,都不会发生死锁。
请注意,我已将setRelation
更改为storeRelation
。 setRelation
会起作用,但为什么会增加这种复杂性呢?
此外,还有一件事我还没有得到:x.setRelation(u_y)
如何无条件地调用x.storeRelation(u_y)
,但调用y.setRelation(u_x)
(或{{1} })只有y.storeRelation(u_x)
还没有关系?这没有意义。似乎需要两个检查,或者都不检查。 (没有看到y
的实现,我无法猜测其中的哪一个。)