Двойная проверка-блокировка гарантирует состояние объекта? (параллелизм на практике)
Я читаю параллелизм на практике и имею некоторое недопонимание.
цитата:
реальная проблема с DCL заключается в предположении, что худшее, что может произойти при чтении ссылки на общий объект без синхронизации, - это ошибочно увидеть устаревшее значение (в данном случае ноль); в этом случае идиома DCL компенсирует этот риск, повторяя попытку, удерживая блокировку. Но наихудший случай на самом деле значительно неправильный - можно увидеть текущее значение ссылки, но устаревшие значения для состояний объекта, что означает, что объект может находиться в недопустимом или неправильном состоянии.
после того, как Брайан Гетц пишет, что DCL будет работать в текущей модели памяти с использованием volatile:
public class DoubleCheckLociing{
private static volatile Resource resource;
public static Resource getInstance(){
if(resource == null){
synchronized(DoubleCheckLociing.class){
if(resource == null){
resource = new Resource();
}
}
}
return resource;
}
}
Я не уверен, что вы правильно поняли фразу о состоянии.
Давайте представим, что Resource
класс выглядит так:
class Resource{
private Date date = new Date();//mutable thread unsafe class
private int k = 10;
public Date getDate(){
return date;
}
public int getK(){
return k;
}
}
Есть ли у меня гарантии, что getInstance
всегда возвращайся correct
ресурс, который всегда возвращает правильный k
(10) и date
?
2 ответа
С volatile
на месте у вас есть эти гарантии. Без volatile
вы не.
Когда один поток записывает переменную volatile resource
операция включает в себя "барьер памяти", который гарантирует, что все, что было написано до этого, например, инициализация полей экземпляра, сначала записывается в системную память.
Когда другой поток читает resource
, он включает в себя барьер памяти, который гарантирует, что никакие чтения, выполненные после этого, не увидят значения, которые были кэшированы из системной памяти перед этим чтением.
Эти два барьера памяти гарантируют, что если поток видит инициализированный resource
переменная, то он также увидит правильно инициализированные поля в этом объекте.
Есть ли у меня гарантии, что getInstance всегда возвращает правильный ресурс, который всегда возвращает правильные k (10) и дату?
И да и нет. Как указывает @Matt, если поле volatile
тогда вам гарантировано, что все поля Resource
будет опубликован надлежащим образом, когда к нему обратится другой поток, который является важной частью примера двойной проверки блокировки, который вы цитируете. volatile
барьеры памяти обеспечивают это.
Тем не менее, ваш Resource
содержит не окончательные поля, которые могут быть изменяемыми (если метод сеттера был добавлен или не указан). Если вы делаете свои поля final
тогда на самом деле вы можете обойтись без volatile
так как final
поля гарантированно будут опубликованы, когда ссылка на ваш Resource
опубликовано.
private final Date date = new Date();
private final int k = 10;
Как вы упоминаете, однако, это не полностью спасает вас, потому что Date
также изменчив. Вы должны убедиться, что ваш код (или другой код) не вызывает никаких методов установки на date
поле, тогда вы будете в порядке.
Это хороший пример того, как важно отслеживать изменчивость в многопоточных программах.