Похоже, что модель памяти Java не определяет «обновление» и «очистку» локального кеша, вместо этого люди называют это так только для простоты, но на самом деле отношение «происходит до» подразумевает как-то обновление и очистку (было бы здорово, если бы вы можете это объяснить, но это не является прямой частью вопроса).
Это меня очень смущает в сочетании с тем фактом, что раздел о Модель памяти Java в JLS написана не так, чтобы ее было легко понять.
Поэтому не могли бы вы сказать мне, верны ли предположения, которые я сделал в следующем коде, и гарантируется ли его корректная работа?
Он частично основан на коде, представленном в статье Википедии о блокировке с двойной проверкой, однако там автор использовал класс-оболочку (FinalWrapper
), но причина этого мне не совсем очевидна. Может быть, для поддержки значений null
?
public class Memoized<T> {
private T value;
private volatile boolean _volatile;
private final Supplier<T> supplier;
public Memoized(Supplier<T> supplier) {
this.supplier = supplier;
}
public T get() {
/* Apparently have to use local variable here, otherwise return might use older value
* see https://jeremymanson.blogspot.com/2008/12/benign-data-races-in-java.html
*/
T tempValue = value;
if (tempValue == null) {
// Refresh
if (_volatile);
tempValue = value;
if (tempValue == null) {
// Entering refreshes, or have to use `if (_volatile)` again?
synchronized (this) {
tempValue = value;
if (tempValue == null) {
value = tempValue = supplier.get();
}
/*
* Exit should flush changes
* "Flushing" does not actually exists, maybe have to use
* `_volatile = true` instead to establish happens-before?
*/
}
}
}
return tempValue;
}
}
Также я читал, что вызов конструктора можно встроить и переупорядочить, что приведет к ссылке на неинициализированный объект (см. этот комментарий в блоге). Безопасно ли тогда напрямую присваивать результат поставщику или это нужно делать в два этапа?
value = tempValue = supplier.get();
Два шага:
tempValue = supplier.get();
// Reorder barrier, maybe not needed?
if (_volatile);
value = tempValue;
Изменить: название этого вопроса немного вводит в заблуждение, цель состояла в том, чтобы сократить использование изменчивого поля. Если инициализированное значение уже находится в кэше потока, то доступ к value
осуществляется напрямую без необходимости повторного поиска в основной памяти.
synchronized
, потому что, согласно JMM, снятие блокировки происходит до последующего получения той же блокировки. - person Ivan   schedule 09.01.2019final
кратко объясняется здесь, в Stack Overflow (также Алексеем Шипилевым). - person Radiodef   schedule 09.01.2019