Преимущества решения Билла Пью над представленной реализацией ленивого инициализированного синглтона связаны с производительностью. Рассмотрим следующий сценарий.
Экземпляр уже инициализирован и , причем два потока одновременно запрашивают экземпляр .
- Для отложенной инициализированной реализации, поскольку метод синхронизирован, один из потоков заблокируется.
- Для реализации Билла Пью блокировки не будет.
В любом случае, это может быть (частично) смягчено путем внедрения синглтона через двойную проверку блокировки. Смотрите пример ниже.
Синглтон с двойной проверкой:
public final class DoubleCheckedLockingSingleton {
private static volatile DoubleCheckedLockingSingleton instance;
private DoubleCheckedLockingSingleton(){
if(instance!=null)
throw new RuntimeException();
}
public static final DoubleCheckedLockingSingleton getInstance(){
if(instance==null){
synchronized(DoubleCheckedLockingSingleton.class) {
if(instance==null)
instance = new DoubleCheckedLockingSingleton();
}
}
return instance;
}
}
В этом случае разница в производительности незначительна. Основное различие между реализацией блокировки с двойной проверкой и реализацией шаблона держателя является причиной его работы.
- Блокировка с двойной проверкой: потоки будут блокироваться, только если они одновременно инициируют создание экземпляра. Вы должны перепроверить для
instance == null
внутри синхронизированного блока, поскольку два потока могут (потенциально, хотя и маловероятно - лучший вид ошибки) поменяться местами между первым if и синхронизированным блоком. Вы также должны объявить переменную volatile, чтобы использовать JMM, который «случается до» гарантии volatile (так что вы уверены, что проверка instance==null
не вернет false, пока экземпляр не будет полностью инициализирован ).
- Паттерн держателя: потоки будут блокироваться, только если они одновременно инициируют создание экземпляра. Все, что вам нужно знать, это то, что классы в Java загружаются лениво, с блокировкой от JVM (поэтому блокировка и видимость выполняются сразу после установки, без дополнительных усилий с вашей стороны).
Лично я предпочитаю шаблон держателя, а не блокировку с двойной проверкой, поскольку причины, по которым он работает, кажутся более понятными (по крайней мере, мне).
В качестве заключительного замечания, если ваши требования позволяют это (например, если вы используете инфраструктуру DI, такую как Spring), лучшим способом реализации синглтона было бы позволить Spring предоставить синглтон для вас (с помощью * 1040). * с одноэлементной областью по умолчанию).