Двойная проверка-блокировка гарантирует состояние объекта? (параллелизм на практике)

Я читаю параллелизм на практике и имею некоторое недопонимание.
цитата:

реальная проблема с 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 поле, тогда вы будете в порядке.

Это хороший пример того, как важно отслеживать изменчивость в многопоточных программах.

Другие вопросы по тегам