Потоковые безопасные синглэты в Java

В статье Википедии о Singletons упоминаются несколько потокобезопасных способов реализации структуры на Java. Для моих вопросов позвольте рассмотреть синглтоны, которые имеют длительные процедуры инициализации и получают сразу несколько потоков.

Во-первых, этот безответный метод потокобезопасен, и если да, то что он синхронизирует?

public class Singleton {
    private Singleton instance;

    private Singleton() {
        //lots of initialization code
    }

    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Во-вторых, почему следующий поток реализации безопасен и ленив при инициализации? Что именно происходит, если два потока входят в метод getInstance() в то же время?

public class Singleton {
    private Singleton() {
        //lots of initialization code
    }

    private static class SingletonHolder { 
        public static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

Наконец, во втором примере, если один поток получает экземпляр первым, а другой поток получает экземпляр и пытается выполнить действия над ним до того, как конструктор закончил в первом потоке? Можете ли вы попасть в небезопасное состояние?

Ответ 1

Ответ 1: static synchronized методы используют объект класса как блокировку - то есть в этом случае Singleton.class.

Ответ 2: Java-язык, среди прочего:

  • загружает классы при первом доступе/использовании
  • гарантирует, что до того, как доступ к классу разрешен, все статические инициализаторы завершили

Эти два факта означают, что внутренний статический класс SingletonHolder не загружается до тех пор, пока не будет вызван метод getInstance(). В этот момент и до того, как потоку, вызывающему вызов, предоставляется доступ к нему, статический экземпляр этого класса создается как часть загрузки класса.

Это означает, что у нас есть безопасная ленивая загрузка, и без необходимости синхронизации/блокировки!

Этот шаблон является шаблоном для использования в одиночных играх. Он превосходит другие шаблоны, потому что MyClass.getInstance() является стандартным отраслевым стандартом для синглтонов - каждый, кто его использует, автоматически знает, что они имеют дело с singleton (с кодом, всегда хорошо, чтобы быть очевидным), поэтому этот шаблон имеет правильный API и правильная реализация под капотом.

btw Статья Билла Пьюга стоит прочитать для полноты при понимании одноэлементных моделей.