我有以下代码:
注意:为了便于阅读,我尽可能简化了代码。
如果我忘记了任何重要内容,请告诉我。

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(..)中的两个锁。

最佳答案

您在这两点上都是正确的:您的不变式确实成立(假设我正确理解了方法名的含义,依此类推,并假设通过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);
        }
    }
}

从而确保如果两个线程都需要同时获得xy的锁定,那么它们要么首先都获得x的锁定,要么它们都首先获得y的锁定。无论哪种方式,都不会发生死锁。

注意,顺便说一句,我将setRelation更改为storeRelationsetRelation可以工作,但是为什么要增加这种复杂性呢?

另外,还有一件事我没有得到:x.setRelation(u_y)为什么会无条件地调用x.storeRelation(u_y),但是仅在y.setRelation(u_x)不存在关系的情况下才调用y.storeRelation(u_x)(或y)?这没有道理。似乎要么需要两项检查,要么都不需要。 (没有看到Relations.storeRelation(...)的实现,我无法猜测是哪种情况。)

10-07 23:35