Я согласен с одним из комментариев Джона: вы всегда должны использовать фиктивную окончательную блокировку при доступе к не конечной переменной, чтобы предотвратить несоответствия в случае изменения ссылки на переменную. Поэтому в любом случае и в качестве первого практического правила:
Правило №1: Если поле не является окончательным, всегда используйте фиктивную (частную) окончательную блокировку.
Причина №1: Вы удерживаете блокировку и самостоятельно меняете ссылку на переменную. Другой поток, ожидающий вне синхронизированной блокировки, сможет войти в защищенный блок.
Причина №2: вы удерживаете блокировку, а другой поток изменяет ссылку на переменную. Результат тот же: другой поток может войти в защищенный блок.
Но при использовании макета финальной блокировки возникает другая проблема : вы можете получить неверные данные, потому что ваш неокончательный объект будет синхронизироваться с ОЗУ только при вызове synchronize (object). Итак, второе практическое правило:
Правило № 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){
}
}
}
}
Как видите, я пишу две блокировки прямо в одной строке, потому что они всегда принадлежат друг другу. Таким образом, вы можете даже сделать 10 блокировок вложенности:
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
}
}
}
}
Обратите внимание, что этот код не сломается, если вы просто установите внутреннюю блокировку, как synchronized (LOCK3)
другие потоки. Но он сломается, если вы вызовете другой поток примерно так:
synchronized (LOCK4) {
synchronized (LOCK1) {
synchronized (LOCK3) {
synchronized (LOCK2) {
}
}
}
}
Существует только один способ обхода таких вложенных блокировок при обработке неокончательных полей:
Правило № 2 - Альтернатива: объявите все поля объекта как изменчивые. (Я не буду здесь говорить о недостатках этого, например о предотвращении любого хранения в кешах уровня x даже для чтения, aso.)
Поэтому aioobe совершенно прав: просто используйте java.util.concurrent. Или начать разбираться в синхронизации и делать это самостоятельно с вложенными блокировками. ;)
Для получения дополнительной информации о том, почему синхронизация не конечных полей прерывается, ознакомьтесь с моим тестовым примером: https://stackoverflow.com/a/21460055/2012947
И для получения более подробной информации, почему вам вообще нужна синхронизация из-за ОЗУ и кешей, смотрите здесь: https://stackoverflow.com/a/21409975/2012947
o
ссылка в момент достижения синхронизированного блока. Если объект, которыйo
ссылается на изменение, может прийти другой поток и выполнить блок синхронизированного кода.