Анализатор обнаружил синхронизацию по объекту, которая может привести к скрытым проблемам параллелизма из-за того, что синхронизированный объект может неявно использоваться в других логически несвязанных частях программы.
Проблема заключается в том, что если производить синхронизацию по:
то это может приводить к потенциальным тупикам и недетерминированному поведению.
Причиной этому может служить то, что вышеперечисленные объекты могут повторно использоваться в разных частях программы.
Суть проблемы синхронизации по приведенным объектам состоит в том, что к объекту, используемому для блокировки, имеется общий доступ. Такой объект может быть использован для блокировки в другом месте без ведома разработчика, использовавшего объект для блокировки в первый раз. Это, в свою очередь, и создаёт вероятность возникновения взаимоблокировки на один и тот же объект.
Приведём синтетический пример взаимоблокировки при синхронизации по 'this':
class SynchroThis { void doSmt() { synchronized(this) { // do smt } } } .... SynchroThis obj = new SynchroThis(); synchronized(obj) { Thread t = new Thread(() -> obj.doSmt()); t.start(); t.join(); } ....
В результате программа никогда не завершится, т.к. происходит deadlock по экземпляру класса SynchroThis (первая блокировка в основном потоке по 'obj', вторая - в потоке 't' по 'this').
Для того, чтобы избежать возможных взаимных блокировок, в качестве объекта блокировки стоит использовать, например, приватное поле:
class A { private final Object lock = new Object(); void foo() { synchronized(lock) { // do smt } } }
Рассмотрим синтетический пример синхронизации по объекту типа Byte:
class FirstClass { private final Byte idLock; .... public FirstClass(Byte id, ....) { idLock = id; .... } .... public void calculateFromFirst(....) { synchronized (idLock) // <= { .... } } } class SecondClass { private final Byte idLock; .... public SecondClass(Byte id, ....) { idLock = id; .... } .... public void calculateFromSecond(....) { synchronized (idLock) // <= { .... } } }
Обусловим, что поток N1 оперирует объектом класса 'FirstClass', а поток N2 - 'SecondClass'.
Теперь давайте рассмотрим сценарий:
Итак, у нас 2 разных потока выполняют совершенно разную логику программы для разных объектов. Что же получится? А получится то, что поток N2 будет находиться в состоянии ожидания до тех пор, пока поток N1 не закончит работу в синхронизированном блоке по объекту 'idLock'. Почему же так получается?
Как и все объекты, переменные созданные с помощью классов оберток будут храниться в куче. У каждого такого объекта будет свой адрес в куче. Но есть небольшой нюанс, который нужно всегда учитывать. Целочисленные классы обертки, полученные при помощи автоупаковки, со значением в диапазоне [-128..127] кэшируются JVM. Поэтому такие обертки с одинаковыми значениями в этом диапазоне будут являться ссылками на один объект.
Так и получается в нашем случае. Синхронизация производится по одному и тому же объекту в памяти, чего вовсе и не ожидалось.
Также, помимо целочисленных классов оберток, не следует использовать для синхронизации объекты классов:
Использование синхронизации по таким объектам небезопасно. Рекомендуется использовать вышеописанный способ с приватным полем, но если по каким-либо причинам Вам это не подходит, то создавайте объекты явно при помощи конструктора. Такой способ гарантирует, что у объектов будут разные адреса. Пример безопасного кода:
class FirstClass { private final Byte idLock; .... public FirstClass(Byte id, ....) { idLock = new Byte(id); .... } .... public void calculateFromFirst(....) { synchronized (idLock) { .... } } } ....
Дополнительную информацию можно посмотреть здесь.
Данная диагностика классифицируется как:
|