пятница, 1 августа 2014 г.

Паттерны проектирования. Singleton.

Это первая из серии статей, которые я хочу написать о шаблонах проектирования. Сегодня речь пойдёт о шаблоне Singleton.

Для начала хочу сказать, что такое вообще Singleton и когда он может быть полезен. Это шаблон, который обеспечивает существование одного экземпляра класса без возможности прямого создания этого класса. Например, в приложении может быть только один менеджер работы с базой данных.



Singleton используют в других шаблонах проектирования, таких как «абстрактная фабрика» (Abstract Factory), Строитель (Builder), Прототип (Prototype), Фасад(Facade) и т.д.

Этот шаблон также используется и самой Java в ее ядре, например в java.lang.Runtime и java.awt.Desktop.


На данный момент существуют несколько вариантов реализации со своими недостатками и преимуществами.

Вариант 1


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Singleton {
    private static Singleton instance;

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

Реализация выше отлично работает, если используется только один поток. Но если дело доходит до многопоточных систем, то это может вызвать проблемы, когда внутри цикла одновременно присутствуют несколько потоков. В этом случае теряется суть шаблона Singleton и два разных потока будут получать разные объекты класса.
Для решения этой проблемы есть следующий вариант.

Вариант 2


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton(){
    }

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

Решение подходит для многопоточных приложений. Но мы потеряли две вещи:
- Ленивую инициализацию (Объект instance будет создан classloader-ом во время инициализации класса) 
- возможность обработки исключений(exceptions) во время вызова конструктора

Вариант 3

Использование внутреннего класса(решение Била Пью(Bill Pugh) “Initialization on Demand Holder”).


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Singleton {
        
    public static class SingletonHolder {
        public static final Singleton HOLDER_INSTANCE = new Singleton();
    }
        
    public static Singleton getInstance() {
        return SingletonHolder.HOLDER_INSTANCE;
    }
}

+ Ленивая инициализация, т.к. объект инициализируется при первом вызове метода getInstance(). 

+ Высокая производительность
- Проблема с обработкой исключительных ситуаций в конструкторе
- Невозможно использовать для не статических полей класса

Вариант 4
Synchronized Accessor


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Singleton {
    private static Singleton instance;
    
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}


У этого варианта есть один недостаток. Синхронизация полезна только один раз, при первом обращении к getInstance(), после этого каждый раз, при обращении этому методу, синхронизация просто забирает время. Но если вызов getInstance() не происходит достаточно часто, то этот метод имеет преимущество перед остальными.

+ Ленивая инициализация

+ Есть возможность обрабатывать исключительные ситуации в конструкторе
- Низкая производительность (критическая секция) в наиболее типичном доступе

Вариант 5
Double Checked Locking & volatile


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Singleton {
        private static volatile Singleton instance;
    
        public static Singleton getInstance() {
        Singleton localInstance = instance;
        if (localInstance == null) {
            synchronized (Singleton.class) {
                localInstance = instance;
                if (localInstance == null) {
                    instance = localInstance = new Singleton();
                }
            }
        }
        return localInstance;
    }
}

+ Ленивая инициализация
+ Высокая производительность 
- Поддерживается только с JDK 1.5

Double Checked Lock можно использовать без исключений с immutable объектами (String, Integer, Float, и т.д.).


Вариант 6

Singleton с помощью Enum
С тех пор как Enum значения могут иметь глобальный доступ они используются в Singleton. Недостатком является то, что возвращаемый Enum тип, несколько негибкий, например, не поддерживает ленивую инициализацию.


1
2
3
4
5
6
7
8
public enum EnumSingleton {
 
  INSTANCE;
   
  public static void doSomething(){
    //do something
  }
}

+ Сериализация из коробки
+ Потокобезопасность из коробки
+ Возможность использования EnumSet, EnumMap и т.д.
+ Поддержка switch
- Не ленивая инициализация

Вот, собственно, и все распространенные варианты имплементаций данного паттерна.


Заключение


Можно выделить следующие советы по использованию того или иного подхода для реализации шаблона Singleton:


1) Использовать нормальную (не ленивую) инициализацию везде где это возможно;

2) Для статических полей использовать On Demand Holder idom;
3) Для простых полей использовать Double Chedked Lock & volatile idom;
4) Во всех остальных случаях использовать Syncronized accessor;


Комментариев нет:

Отправить комментарий