Является ли это лучшей версией двойной проверки блокировки без энергозависимости и синхронизации
Ниже приведен фрагмент кода из эффективной блокировки Java 2-й редакции, дважды проверенной
// Двойная проверка идиомы для ленивой инициализации полей экземпляра
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) { // First check (no locking)
synchronized(this) {
result = field;
if (result == null)// Second check (with locking)
field = result = computeFieldValue();
}
}
return result;
}
Из того, что я знаю, основной проблемой с двойной проверкой блокировки является переупорядочение внутри второй проверки блокировки, чтобы другой поток мог видеть значения поля / результата в виде набора, которые могут быть неэффективными при выполнении. Чтобы избежать этого, мы делаем ссылку на поле как изменчивую, чтобы гарантировать видимость и переупорядочение.
Но это может быть достигнуто с помощью приведенного ниже кода также
private FieldType field; // non volatile
private volatile boolean fence = false;
FieldType getField() {
if (field == null) { // First check (no locking) // no volatile read
synchronized(this) { // inside synch block no problem of visibilty will latest //value of field
if (field == null) {// Second check (with locking)
Object obj = computeFieldValue();
fence = true; // any volatile write will take. this will make sure statements are //not reorder with setting field as non null.
field = (FieldType)obj; // this will be only set after computeFieldValue has been //completed fully
}
}
}
return field;
}
Таким образом, после того, как инициализация будет выполнена, никакой поток не будет нуждаться в энергозависимом чтении или накладных расходах синхронизации. Пожалуйста, посмотрите, правильны ли мои предположения или нет?
3 ответа
Невозможно добиться "дешевой" двойной проверки блокировки в чистом JMM; что-то должно дать.
Ваше решение не работает, потому что изменчивая запись может быть переупорядочена с помощью следующего обычного действия. См. Jsr133 кулинарную книгу о разрешенном переупорядочении в так называемой модели " Roach Motel ". "Roach Motel" - более сильная модель, чем JMM, поэтому, если ваше решение не работает в Roach Motel, оно терпит неудачу в JMM.
roach motel model
reordering between a normal action and a volatile/monitor action
-- <--
| |
| VolatileLoad / MonitorEnter | forbidden
| |
--> allowed --
--> allowed --
| |
| VolatileStore / MonitorExit | forbidden
| |
-- <--
Существует способ предотвратить изменение порядка двух обычных действий в модели "Roach Motel"
(1) action#1
(2) volatile write
(3) volatile read
(4) action#4
(1) не может быть переупорядочено с (2), и (4) не может быть переупорядочено с (3), поэтому (1) и (4) не могут быть переупорядочены.
Тем не менее, имейте в виду, что модель "Roach Motel" является более сильной моделью, чем JMM. Вы не можете быть уверены, что JVM соответствует модели мотеля. Для конкретного примера
action#1
synchronized(new Object()){}
synchronized(new Object()){}
action#4
согласно мотелю Роуч, действие № 1 и действие № 4 не могут быть переупорядочены; однако JVM может законно (разрешено JMM) удалить два блока синхронизации, а затем изменить порядок оставшихся двух действий.
JLS (раздел 17.4.5) гласит:
"Запись в энергозависимое поле (§8.3.1.4) происходит - перед каждым последующим чтением этого поля".
Вы не читаете fence
переменная после того, как она обновлена, таким образом, нет никакой связи "случается до" между потоком, который обновляет fence
и любой второй поток. Это означает, что второй поток не может видеть обновления field
переменная, сделанная первым потоком.
Короче говоря, ваш "улучшенный" код - неработающая реализация двойной проверки блокировки.
Согласно источникам, которым я доверяю, это кажется работающим и безопасным кодом. Запись изменяемой переменной должна обеспечивать запись всех остальных переменных и не может быть переупорядочена ни с помощью volatile, ни с обычными присваиваниями.