Шаблон "Одиночка" гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа. Существенно то, что можно пользоваться именно экземпляром класса, так как при этом во многих случаях становится доступной более широкая функциональность.
Рассмотрим варианты реализации одиночки на Java. При этом будем обращать внимание на многопоточность и связанные с ней возможные проблемы. Изложенное ниже следует статье в англоязычной Википедии Singleton pattern. Также использовались некоторые другие источники.
Самая простая реализация:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
// Private constructor prevents instantiation from other classes
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
Эта реализация потокобезопасна. Объект INSTANCE создается тогда, когда класс инициализируется. Например, это может произойти, когда будет вызван какой-нибудь статический метод класса. Конструктор объявляется private, доступ к экземпляру класса возможен только через getInstance(). Такая реализация, однако, может не подойти, если создание экземпляра класса требует много ресурсов. В этом случае нужно подумать об отложенной инициализации синглетона.
Вариант Уильяма Пу (William Pugh)
Это вариант отложенной инициализации (или еще говорят: ленивой инициализации)
public class Singleton {
// Private constructor prevents instantiation from other classes
private Singleton() {
}
/**
* SingletonHolder is loaded on the first execution of Singleton.getInstance()
* or the first access to SingletonHolder.INSTANCE, not before.
*/
private static class SingletonHolder {
public static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
Эта реализация "ленива" настолько, насколько это возможно. Здесь используется идиома Initialization on demand holder idiom - т.е. отложенная инициализация с использованием объекта-холдера.
Несмотря на отсутствие synchronized в коде, эта реализация потокобезопасна. Такая реализация Одиночки работает на всех версиях Java. Здесь используются та часть спецификации Java (см. 8.3.1.1 static Fields и 12.4.2 Detailed Initialization Procedure), в которой описывается порядок инициализации класса. При инициализации класса Java-машина сама заботится о синхронизации, т.к. нет нужды подставлять использовать synchronized.
Т.к. на внутренний класс SingletonHolder нет ссылок нигде, кроме метода getInstance, то и инциализирован он будет не ранее, чем метод getInstance будет вызван. При такой инициализации Java-машина сама позаботится о синхронизации. Так что этот вариант - пример отличной потокобезопасной реализации паттерна Одиночка.
Реализация с double-checked locking (DCL)
Рассмотрим еще одну реализацию. Эта реализация использует двойную проверку блокировки. Она будет корректно работать в многопоточной среде только начиная с версии Java 5, т.к. в Java 5 была изменена модель памяти. Ранее существовавшая модель памяти не позволяла корректно выполняться такой реализации в многопоточной среде. Вот код:
public class Singleton {
// volatile is needed so that multiple thread can reconcile the instance
// semantics for volatile changed in Java 5.
private volatile static Singleton singleton;
private Singleton() {
}
// synchronized keyword has been removed from here
public static Singleton getSingleton() {
// needed because once there is singleton available no need to acquire
// monitor again & again as it is costly
if(singleton==null) {
synchronized(Singleton.class){
// this is needed if two threads are waiting at the monitor at the
// time when singleton was getting instantiated
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
Однако, лучше не использовать double-checked locking. Это старый прием, который был вызван особенностями ранних JVM. Теперь его иногда даже рассматривают как анти-паттерн.
Подводя итоги, можно рекомендовать релализацию синглетона с помощью с использованием объекта-холдера.
Комментариев нет:
Отправить комментарий