Изменчивый читает столкновение - PullRequest
0 голосов
/ 22 октября 2018

Предположим, что мы создаем экземпляр синглтона с двойной проверкой блокировки:

public static Instance getInstance() {
    if (this.instance == null) {
        synchronized(Instance.class) {
            if (this.instance == null) {
                this.instance = new Instance();
            }
        }
    }
    return this.instance;
}

Вопрос в семантике программы, если переменная instance будет volatile и double-проверенная блокировка будет удалена.

private volatile static Instance instance;

public static Instance getInstance() {
    if (this.instance == null) {
        this.instance = new Instance();
    }
    return this.instance;
}

Будет ли экземпляр класса создан только один раз?Или, другими словами, может ли volatile считывать конфликты таким образом, что два потока увидят null значение ссылки, и будет выполнено двойное создание экземпляра?

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

PS: вопрос заключается не в применении шаблона Singleton (это был просто пример, где проблема ясна), речь идет о том, можно ли заменить блокировку с двойной проверкой на volatile read - volatile write без программысемантика меняется, не более того.

Ответы [ 4 ]

0 голосов
/ 22 октября 2018

Используйте enum, если вам действительно нужен синглтон в Java.Это исправит для вас следующее:

enum MySingleton {
    INSTANCE;

    public static MySingleton getInstance() {
        return INSTANCE;
    }
}

JVM инициализирует экземпляр безопасным для потока способом при первом доступе к MySingleton.Единственный способ получить более одного его экземпляра - это если у вас несколько загрузчиков классов.

0 голосов
/ 22 октября 2018

Да, действительно: volatile чтения может конфликтовать таким образом, что два потока увидят нулевое значение ссылки, и будет выполнена двойная реализация.

Вам также нужна инициализация с двойной скобкой и volatile.Это потому, что когда instance становится ненулевым, вы не синхронизируете другие потоки ни перед чем, читая его - сначала if просто заставляет их идти дальше, возвращая значение unsynchronized (даже если инициализирующий поток не экранируетсинхронизированный блок еще), это может привести к последующему чтению потока не инициализированной переменной из-за отсутствия синхронизации.Синхронизация для правильной работы должна выполняться каждым потоком, обращающимся к данным, которыми он управляет, DCL пропускает синхронизацию после инициализации, что является неправильным.Вот почему вам нужен дополнительный volatile для работы DCL, тогда volatile будет гарантировать, что вы прочитали инициализированное значение.

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

Суммируя это вместе: без надлежащей синхронизации процессор может инициализировать ссылку на instance как ненулевую, но instance не может быть полностью инициализирована внутри, поэтому последующаячтение потока может читать неинициализированный объект и вести себя неправильно из-за этого.

0 голосов
/ 22 октября 2018

Учитывая этот код.

private volatile static Instance instance;

public static Instance getInstance() {
    if (this.instance == null) {
        this.instance = new Instance();
    }
    return this.instance;
}

Из вашего вопроса:

Will the class get instantiated only once? Can volatile reads clash in such way that two threads will see null value of the reference and double instantiation will be performed?

Волатильные чтения не могут конфликтовать таким образом из-за гарантий JMM.Тем не менее, вы все равно можете получить два экземпляра, если несколько потоков поменяются местами после if, но перед началом создания экземпляра volatile-переменной.

if (this.instance == null) {
    // if threads swap out here you get multiple instances
    this.instance = new Instance();
}

Чтобы вышеописанный сценарий не состоялся, вы должны использовать двойную проверку блокировки

if (this.instance == null) {
    // threads can swap out here (after first if but before synchronized)
    synchronized(Instance.class) {
        if (this.instance == null) {
            // but only one thread will get here
            this.instance = new Instance();
        }
    }
}

Обратите внимание, что здесь необходимо учитывать два аспекта.

  • Атомность: нам нужно убедиться, что второе if и создание экземпляра происходят атомарным образом (вот почему нам нужен блок synchronized).
  • Видимость: нам нужно убедиться, что ссылка на переменную экземпляра не переходит в несовместимое состояние (вот почему нам нужно объявление volatile для переменной экземпляра, чтобы использовать JMMбывает до гарантии).
0 голосов
/ 22 октября 2018

Без синхронизации ваш код определенно нарушен, потому что 2 потока могут увидеть, что значение instance равно нулю, и оба будут выполнять инициализацию (рассмотрите переключение контекста в каждой строке и посмотрите, что произойдет.

Кроме того, даже с синхронизацией двойная проверка блокировки (DCL) в прошлом считалась нарушенной в Java, потому что при работе без синхронизации второй поток может испытывать операции инициализации в другом порядке. Вы можете исправить свой код, добавив локальную переменную изагружайте энергозависимый в него всякий раз, когда вы хотите его прочитать:

public static Instance getInstance() {
    Instance tmp = instance;
    if (tmp == null) {
        synchronized(Instance.class) {
            Instance tmp = instance;
            if (tmp == null) {
                instance = new Instance();
            }
        }
    }
    return instance;
}

, но более безопасное решение будет использовать ClassLoader в качестве механизма синхронизации, а также позволит вам прекратить использовать медленный энергозависимый доступ каждый раз, когда выДоступ к синглтону:

public class Instance {

    private static class Lazy {
        private static Instance INSTANCE = new Instance();    
    }

    public static Instance getInstance() {
        return Lazy.INSTANCE;
    }
}

INSTANCE будет инициализирован только тогда, когда первый поток введет getInstance()

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...