Двойная проверка блокировки без использования 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 и т. Д.) Переписывают код. Ваше временное решение может быть легко удалено. Мне трудно поверить, что конструктор может вернуть частичный объект, но если знающие авторы так говорят, я бы доверял их мнению.