Двойная проверка блокировки без использования volatile-ключевого слова и без синхронизации всего метода getInstance()

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

public class MySingleton {

    private static MySingleton mySingleton;

    public static MySingleton getInstance() {
        if(mySingleton == null) {
            synchronized(MySingleton.class) {
                if(mySingleton == null) {
                    MySingleton temp = new MySingleton();
                    mySingleton = temp;
                }
            }
        }

        return mySingleton;
    }
}

По мне, это потокобезопасно. Если кто-то думает, что это не потокобезопасно, может кто-нибудь объяснить, почему он / она думает, что это не потокобезопасно? Благодарю.

3 ответа

Да, но я использую переменную "temp". Разве это не решает проблему "частично созданного объекта"?

Нет.

Предположим, некоторые вызовы потока getInstance()и заканчивает тем, что создал новый экземпляр и назначил mySingleton переменная. Затем приходит поток T, вызывает getInstance() и видит это mySingleton не является null,

На данный момент поток T не использовал синхронизацию Без синхронизации спецификация языка Java (JLS) не требует, чтобы поток T видел назначения, сделанные потоком A, в том же порядке, в котором их сделал поток A.

Давайте предположим, что у объекта singleton есть некоторые переменные-члены. Поток A, очевидно, должен был инициализировать эти переменные, прежде чем сохранить ссылку в mySingleton, Но JLS позволяет потоку T видеть mySingleton != null и все же по-прежнему видеть переменные-члены в их неинициализированном состоянии. На некоторых многоядерных платформах это действительно может произойти.

Присвоение ссылки на объект локальному temp переменная first ничего не меняет. На самом деле, как указал Steve11235, temp переменная может даже не существовать в байтовых кодах или в нативных инструкциях, потому что компилятор Java или компилятор горячих точек могут полностью ее оптимизировать.

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

Если вам по какой-то причине необходимо продолжить работу с приведенным выше кодом, одним из решений является объявление переменной как volatile вот так:

private static volatile MySingleton mySingleton;

Или же сделайте чтение переменной synchronized, Любой из них решит вашу проблему за счет производительности.

Еще одним важным моментом является то, что ваш temp переменная не имеет смысла здесь. Это просто избыточный код и ничего не делает. Вместо этого непосредственно назначьте его mySingleton переменная, как так,

mySingleton = new MySingleton();

Все вышеперечисленные проблемы можно легко решить с помощью enum типа делать синглтоны. Поскольку перечисления по своей природе Serializableнам не нужно реализовывать это с Serializable интерфейс. Проблема отражения также не существует. Таким образом, на 100% гарантируется, что только один экземпляр Singleton присутствует в JVM. Таким образом, этот метод рекомендуется как лучший способ создания синглетонов в Java.

Я не знал об этой проблеме, пока не прочитал все комментарии. Проблема в том, что различные процессы оптимизации (компилятор, Hot Spot и т. Д.) Переписывают код. Ваше временное решение может быть легко удалено. Мне трудно поверить, что конструктор может вернуть частичный объект, но если знающие авторы так говорят, я бы доверял их мнению.

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