Является ли это лучшей версией двойной проверки блокировки без энергозависимости и синхронизации

Ниже приведен фрагмент кода из эффективной блокировки 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, ни с обычными присваиваниями.

Другие вопросы по тегам