Получение нескольких блокировок атомарно в Java - PullRequest
4 голосов
/ 04 марта 2012

У меня есть следующий код: Примечание: я максимально упростил код для удобства чтения. Если я забыл какие-нибудь критические статьи, дайте мне знать.

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 (..).

1 Ответ

8 голосов
/ 04 марта 2012

Вы правы в обоих пунктах: ваш инвариант остается верным (при условии, что я правильно понимаю, что означают имена ваших методов и т. Д., И при условии, что под 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) (или y.storeRelation(u_x)), только если y не делает Т уже есть отношения? Это не имеет смысла. Похоже, либо необходимы обе проверки, либо , либо проверка не нужна. (Не видя реализации Relations.storeRelation(...), я не могу догадаться, какой из них имеет место.)

...