Сброс поля с отложенной загрузкой с двойной проверкой - PullRequest
5 голосов
/ 21 ноября 2008

Рассмотрим «двойную проверку идиомы для ленивой инициализации полей экземпляра»:

// Item 71 in Effective Java copied from <a href="http://java.sun.com/developer/technicalArticles/Interviews/bloch_effective_08_qa.html" rel="nofollow noreferrer">this interview with Bloch</a>.
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;
}

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

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

Ответы [ 5 ]

4 голосов
/ 21 ноября 2008

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

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

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

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

3 голосов
/ 21 ноября 2008

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

0 голосов
/ 23 января 2013

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

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

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

FieldType result = field;<br/>if (result == null) { // First check (no locking)

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

0 голосов
/ 26 ноября 2008

Полагаю, это зависит от того, что именно вы подразумеваете под поточно-ориентированным

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

0 голосов
/ 21 ноября 2008

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

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