Как разрешить этот тупик?

Я добавил комментарии в код, чтобы объяснить, откуда возникает взаимоблокировка. По сути, есть две нити. Каждый поток получает блокировку объекта Manager, а затем переходит к получению блокировки статического ресурса, который представляет собой карту всех объектов Manager в приложении. Оба потока вызывают get() на карте. Класс Manager переопределил метод equals(). equals() далее вызывает некоторый синхронизированный метод класса Manager. Таким образом, get() на карте потребуется блокировка уровня объекта для каждого объекта на карте один за другим, пока ключ не совпадет, потому что равенство переопределяется. Я могу изменить код только в подклассах (Sub1 и Sub2) и избежать взаимоблокировки, так как у меня нет доступа к другим классам.

Редактировать: у меня нет доступа к syncMap. Код в «синхронизированном» блоке выполняется в стороннем коде, API которого я вызываю.

Могу ли я избежать этого, установив блокировку в finally на Manager, а не перед блоком try ?!

    public class Parent{
        protected Manager manager;
    }

    public class Global{
        private static final Map syncMap = Collections.synchronizedMap(new HashMap());
        //syncMap contains all the objects of Manager in the application
    }

    class Manager{


        public boolean equals(Object o){

            Manager obj = (Manager)o;
            return obj.getURL().equals(getURL());
        }

        public final synchronized String getURL(){
            return msettings.getDBURL(); //msettings is a global variable
        }


    }


    //Thread-1 is executing someMethod() of this class

    class Sub1 extends Parent{
        Global global;
        //consider manager and Global object are not null
        public void someMethod()
        {
            synchronized(manager){// Thread-1 succesfully takes object level lock on a manager object, say Manager01
                try{
                    global.syncMap.get(manager);
                    // Thread-1 Succesfully takes class level lock on syncMap
                    //  get() calls equals() for each object in syncMap. 
                    //equals() need object lock on each Manager Object in map as it further calls synchronized getURL()
                    // But on one manager Object(Manager02) Thread-2 has already acquired lock and is waiting for lock on syncMap which this thread-1 holds

                }
                finally{
                    manager.releaseConnection();
                }

            }
        }
    }

    //Thread-2 is executing otherMethod() of this class
    class Sub2 extends Parent{ 
        public void otherMethod()
        {
            synchronized(manager){// this takes a lock on manager(Manager02)
                try{
                    global.syncMap.get(manager);
                    // this is blocked as syncMap is aquired by thread-1


                }
                finally{
                    manager.releaseConnection();
                }

            }
        } 
    }

person user5415672    schedule 18.03.2016    source источник
comment
почему вы думаете, что будет тупик? этот код применяет упорядоченную внутреннюю блокировку с синхронизацией (менеджером), поэтому я не вижу взаимоблокировки...   -  person nukie    schedule 18.03.2016
comment
Я сталкиваюсь с тупиком в моей организации работодателя. И дамп потока говорит, что это происходит только здесь. Thread-1 имеет блокировку на уровне класса в syncMap и ожидает блокировки экземпляра класса Manager, доступного в SyncMap. Другой поток уже заблокировал объект Manager и ожидает блокировки SyncMap. Менеджер может иметь много экземпляров, и все экземпляры находятся в SyncMap для записи.   -  person user5415672    schedule 18.03.2016
comment
Нет смысла насильно избавляться от дизайна, в котором equals выполняет синхронизированные действия. В лучшем случае оценка equals в каком-либо клиентском коде скажет вам, какое состояние было во время вызова equals, и не обещает состояние после вызова. Вместо этого реализуйте атомарные операции (trySomeOperation), которые сообщают вызывающему объекту, были ли требуемые условия такими, как ожидалось, и операция была выполнена.   -  person BeyelerStudios    schedule 18.03.2016
comment
как создаются объекты Sub1 и Sub2? Они держат один и тот же экземпляр менеджера? Можете ли вы описать, как вы запускаете свои темы?   -  person nukie    schedule 18.03.2016
comment
Sub1 и sub2 имеют разные объекты менеджера. Объекты менеджера возвращаются некоторым вызовом API, к которому у нас нет доступа. API отслеживает все объекты менеджера через карту. Для простоты я не показывал вызовы API. Я просмотрел код API с помощью декомпилятора Java и добавил этот код в синхронизированный блок моего кода, чтобы упростить публикацию здесь.   -  person user5415672    schedule 18.03.2016


Ответы (2)


После новой порции информации другого решения, кроме как перевести всю обработку в последовательный стиль, я не вижу. Таким образом, вы можете поместить все вызовы API, связанные с менеджером, в один синхронизированный метод некоторого класса-оболочки и использовать эту оболочку как единую точку входа для стороннего API.

class BrutalWrapper {
    public synchronized void doIt(Manager manager)
    {
        try{
            global.syncMap.get(manager);

        }
        finally{
            manager.releaseConnection();
        }
    }
}

class Sub1 extends Parent{
    BrutalWrapper brutal;
    public void someMethod()
    {
        brutal.doIt(manager);
    }
}

class Sub2 extends Parent{
    BrutalWrapper brutal;
    public void someMethod()
    {
        brutal.doIt(manager);
    }
}
person nukie    schedule 18.03.2016
comment
У меня нет доступа к syncMap. Код, который я поместил в блок синхронизации, выполняется в вызове API, к которому у меня нет доступа. У меня есть доступ только к объекту менеджера - person user5415672; 18.03.2016
comment
вы сказали, что можете изменить классы Sub1 и Sub2, и вы показали глобальный объект как часть класса Sub1 и вызов метода global.syncMap.get в Sub1... тогда вы должны быть более конкретными в своем вопросе - person nukie; 18.03.2016
comment
тогда какой класс/метод действительно может получить доступ к объекту syncMap? - person nukie; 18.03.2016
comment
@nukie: вам нужно либо убедиться, что Sub1 и Sub2 используют один и тот же экземпляр BrutalMapper, либо сделать метод doIt статическим. Иначе это не сработает... - person gustf; 18.03.2016
comment
@gustf на самом деле я упомянул, что: ... и использовать эту оболочку как единую точку входа для стороннего API - person nukie; 18.03.2016
comment
Хорошо, что вы уже подумали об этом. Возможно, было бы неплохо сделать это более понятным... может быть, что-то вроде: и использовать один экземпляр этой оболочки... - person gustf; 18.03.2016

Прежде всего, вам действительно следует попытаться исключить необходимость синхронизации в методе equals. Это создаст больше проблем, чем решит, поэтому, если возможен редизайн, я думаю, что это лучший способ.

Однако, если вы немного реструктурируете код и переместите global.syncMap.get(manager) перед блоком synchronization, это не приведет к взаимоблокировке.

public Class Parent{
    protected Manager manager;
}

class Global{
    private static final Map syncMap = Collections.synchronizedMap(new HashMap());
    //syncMap contains all the objects of Manager in the application
}

class Manager{


    public boolean equals(Object o){

        Manager obj = (Manager)o;
        return obj.getURL().equals(getURL());
    }

    public final synchronized String getURL(){
        return msettings.getDBURL(); //msettings is a global variable
    }


}


//Thread-1 is executing someMethod() of this class

class Sub1 extends Parent{
    Global global;
    //consider manager and Global object are not null
    public void someMethod()
    {
         try {
             global.syncMap.get(manager);
             synchronized(manager){

             }
         }
         finally{
             manager.releaseConnection();
         }            
    }
}

//Thread-2 is executing otherMethod() of this class
class Sub2 extends Parent{ 
    public void otherMethod()
    {
         try {
             global.syncMap.get(manager);
             synchronized(manager){

             }
         }
         finally{
             manager.releaseConnection();
         }                        
    } 
}

ОБНОВЛЕНИЕ Альтернативная синхронизация через Global.class, вероятно, может также использовать переменную экземпляра global вместо Global.class

ОБНОВЛЕНИЕ Синхронизация изменена на Manager.class вместо Global.class.

class Sub1 extends Parent
{
    Global global;

    public void someMethod()
    {
        synchronized (Manager.class) { 
            try {
                global.syncMap.get(manager);
            }
            finally {
                manager.releaseConnection();
            }
        }
    }
}


class Sub2 extends Parent
{
    Global global;

    public void otherMethod()
    {
        synchronized (Manager.class) { 
            try {
                global.syncMap.get(manager);
            }
            finally {
                manager.releaseConnection();
            }
        }
    }
}
person gustf    schedule 18.03.2016
comment
при этом вы позволяете двум потокам работать с одними и теми же данными менеджера, поэтому могут возникнуть новые проблемы с параллелизмом: как я вижу, исходная идея заключалась в том, чтобы поместить вызов метода manager.releaseConnection() внутри синхронизированного блока... - person nukie; 18.03.2016
comment
Спасибо. Я не могу изменить дизайн. У меня есть доступ только к подклассам. И фактический синхронизированный блок, как показано ниже, делает вызов API. Внутри этого API происходит все плохое. Я извлек код из API и вставил синхронизированный блок, задав вопрос для простоты. синхронизированный (менеджер) { try { fdb1Objssert = routedDrug.getFDBClassifications(); } наконец { manager.returnConnection(); } - person user5415672; 18.03.2016