Сброс поля с отложенной загрузкой идиомой двойной проверки

StackOverflow https://stackoverflow.com/questions/307073

Вопрос

Подумайте об идиоме двойной проверки для ленивой инициализации полей экземпляра:

// Item 71 in Effective Java copied from this interview with Bloch.
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;
}

Я хочу иметь возможность сбросить поле безопасным способом (в моем случае принудительно загрузить его снова из базы данных). Я предполагаю, что мы могли бы сделать это, используя метод сброса:

void reset() {
   field = null;
}

Это стандартный способ сброса поля? Это безопасно? Есть подводные камни? Я спрашиваю, потому что Блох дал следующее предупреждение о двойной проверке отложенной загрузки: " идиома очень быстрая, но также сложная и деликатная, так что не поддавайтесь искушению изменить ее каким-либо образом. Просто скопируйте и вставьте - обычно это не очень хорошая идея, но уместно здесь.

Заранее спасибо, Плайя из Гималаев.

Это было полезно?

Решение

Да, это потокобезопасно.

Синхронизированный блок предназначен для предотвращения ненужного вызова несколькими потоками computeFieldValue () . Поскольку field является изменчивым, доступ в reset и getField все упорядочен.

Если первая проверка не равна нулю, выполняется getField ; результат возвращается.

В противном случае блокировка получена, за исключением любого другого потока, который может установить для поля значение, отличное от NULL, но позволяющего любому потоку установить для field значение NULL. Если какой-либо поток устанавливает для field значение null, ничего не должно было измениться; это условие, которое привело поток в синхронизированный блок. Если другой поток уже получил блокировку после проверки текущего потока и установил для поля ненулевое значение, вторая проверка обнаружит это.

Другие советы

Я думаю, что это должно быть безопасно, но только потому, что вы храните поле в локальной переменной. После этого ссылка на локальную переменную не может волшебным образом измениться на ноль, даже если другой поток сбрасывает значение поля на полпути.

Похоже, это будет работать до тех пор, пока в качестве метода сброса используется метод reset () , указанный выше. Однако, если метод reset () создает новый объект (как показано ниже), не могли бы вы в конечном итоге вернуть что-то другое, чем вы предполагали?

void reset() {
    field = new FieldType();
}

Я думаю, это зависит от того, что именно вы подразумеваете под потокобезопасностью.

Вы можете столкнуться с ситуацией, когда первый экземпляр используется после второго. Это может быть нормально, а может и нет.

Я думаю, что метод reset () неправильный. Если вы прочитаете Item 71, вы найдете:

Этот код может показаться немного запутанным. В частности, необходимость местного переменный результат может быть неясным. Эта переменная должна гарантировать, что поле только для чтения один раз в общем случае, когда он уже инициализирован.

Ленивая инициализация не предполагает, что поле может измениться. Если поле будет установлено равным нулю между этими операторами:

FieldType result = field;
if (result == null) {// Первая проверка (без блокировки)

getField () предоставляет неверный результат.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top