Immutability and reordering
Приведенный ниже код (Java Concurrency in Practice, листинг 16.3) не является поточно-ориентированным по понятным причинам:
public class UnsafeLazyInitialization {
private static Resource resource;
public static Resource getInstance() {
if (resource == null)
resource = new Resource(); // unsafe publication
return resource;
}
}
Однако несколькими страницами позже, в разделе 16.3, они заявляют:
UnsafeLazyInitialization
на самом деле безопасноесли Resource
неизменен.
Я нене понимаю это утверждение:
ЕслиResource
неизменен, любой поток, наблюдающийresource
переменная будет иметь нулевое значение или полностью сконструирована (благодаря строгим гарантиям конечных полей, предоставляемых моделью памяти Java)Однако ничто не мешает переупорядочению команд: в частности, два чтенияresource
может быть переупорядочен (есть одно чтение вif
и один вreturn
). Таким образом, поток мог видеть ненулевойresource
вif
условие, но возвращает нулевую ссылку (*).Я думаюUnsafeLazyInitialization.getInstance()
может вернуть ноль, даже еслиResource
неизменен. Это так и почему (или почему нет)?
(*) Чтобы лучше понять мою мысль о переупорядочении,этот блог Джереми Мэнсон, один из авторов главы 17 JLS о параллелизме, объясняет, как String 's хеш-код безопасно публикуется через безопасную гонку данных, и как устранение использования локальной переменной может привести к тому, что хеш-код неверно вернет 0, из-за возможного переупорядочения, очень похожего на то, что я описал выше:
Что я'мы сделали, чтобы добавить дополнительное чтение: второе чтение хэша, перед возвратом. Как бы странно это ни звучало и как бы маловероятно ни было, первое чтение может вернуть правильно вычисленное значение хеш-функции, а второе чтение может вернуть 0! Это разрешено в рамках модели памяти, потому что модель допускает обширное переупорядочение операций. Второе чтение фактически может быть перемещено в вашем коде, так что ваш процессор сделает это раньше первого!