Я согласен с одним из комментариев Джона: вы должны всегда использовать окончательную блокировку манекена при доступе к нефинальной переменной, чтобы предотвратить несоответствия в случае изменения ссылки на переменную.Таким образом, в любом случае и в качестве первого практического правила:
Правило № 1: Если поле не является окончательным, всегда используйте (приватный) фиктивный последний блокирующий манекен.
Причина № 1: Вы удерживаете блокировку и самостоятельно изменяете ссылку на переменную.Другой поток, ожидающий вне синхронизированной блокировки, сможет войти в защищенный блок.
Причина № 2: Вы удерживаете блокировку, и другой поток изменяет ссылку на переменную.Результат тот же: другой поток может войти в защищенный блок.
Но при использовании окончательной фиктивной блокировки возникает другая проблема : вы можете получить неверные данныепотому что ваш неконечный объект будет синхронизироваться только с ОЗУ при вызове синхронизации (объект).Итак, в качестве второго практического правила:
Правило № 2: При блокировке неконечного объекта вы всегда должны выполнять оба действия: использование фиктивной фиктивной блокировки и фиксацию не финального объектаради синхронизации ОЗУ. (Единственной альтернативой будет объявление всех полей объекта как энергозависимых!)
Эти блокировки также называются «вложенными блокировками». Обратите внимание, что вы должны вызывать их всегда в одном и том же порядке, в противном случае вы получите мертвую блокировку :
public class X {
private final LOCK;
private Object o;
public void setO(Object o){
this.o = o;
}
public void x() {
synchronized (LOCK) {
synchronized(o){
//do something with o...
}
}
}
}
Как вы видите, я пишу две блокировки прямо в одной строке,потому что они всегда принадлежат друг другу.Таким образом, вы можете даже сделать 10 вложенных блокировок:
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
//entering the locked space
}
}
}
}
Обратите внимание, что этот код не сломается, если вы просто получите внутреннюю блокировку, такую как synchronized (LOCK3)
, другими потоками.Но он сломается, если вы вызовете в другом потоке что-то вроде этого:
synchronized (LOCK4) {
synchronized (LOCK1) { //dead lock!
synchronized (LOCK3) {
synchronized (LOCK2) {
//will never enter here...
}
}
}
}
Существует только один обходной путь для таких вложенных блокировок при обработке неконечных полей:
Правило № 2 - Альтернатива: Объявить все поля объекта как volatile. (Я не буду здесь говорить о недостатках этого, например, о предотвращении хранения в кеше уровня x даже для операций чтения, также.)
Итак, поэтому aioobe совершенно прав: просто используйте java.util.concurrent.Или начинайте понимать все о синхронизации и делайте это самостоятельно с помощью вложенных блокировок.;)
Для получения более подробной информации о причинах прерывания синхронизации в нефинальных полях посмотрите мой контрольный пример: https://stackoverflow.com/a/21460055/2012947
И более подробно, почему вам вообще нужна синхронизация из-за ОЗУи кеши смотрите здесь: https://stackoverflow.com/a/21409975/2012947