2-я реализация не является поточно-ориентированной. Простой способ показать это - поместить Thread.sleep(10);
(или более) в конструктор Singleton
.
. Это заставит первый поток установить isInitialized
в true
(в то время как instance
равно нулю), затем вызовите new Singleton();
.
Другой поток увидит instance
как null
, он не войдет в блок if
, потому что логическое значение теперь true
, затемвернет instance
, что является нулем.
Таким образом, условие гонки заканчивается на NullPointerException
.
Если бы мы заставили это решение перевести в рабочее, оно должно было бы использоватьspinlock и может быть что-то вроде этого (это утренний код, поэтому дайте мне знать, если что-то странное):
public static Singleton getInstance() {
// Fast-path when singleton already constructed
if(instance != null)
return instance;
// Spinlock lets the first thread through, others will spin
while(instance == null && !isInitialized.compareAndSet(false, true))
;
// First thread creates the singleton, spun threads will ignore
if(instance == null)
instance = new Singleton();
return instance;
}
также instance
должно быть volatile
для обеспечения видимости.
Первая блокировка, которая будет введена, очистит спин-блокировку, потому что даже если экземпляр имеет значение null, !compareAndSet
вернет false (как это происходит в первый раз).
После этого любой входящий поток останется вспин-блокировка, потому что instance == null
и !compareAndSet
равны true
. Когда создание экземпляра завершено, спин-блокировка всегда будет проваливаться из-за первого условия.
Это в основном DCL со спин-блокировкой вместо синхронизированной, и нет сценария, в котором я думаю, что приведенный выше код будет полезен.