Сінглтон в Java

1. Вступ

У цій короткій статті ми обговоримо два найпопулярніших способи реалізації Singletons у звичайній Java.

2. Сінглтон на основі класу

Найпопулярніший підхід - це реалізація Singleton, створюючи звичайний клас і переконуючись, що він має:

  • Приватний конструктор
  • Статичне поле, що містить його єдиний екземпляр
  • Статичний фабричний метод отримання екземпляра

Ми також додамо інформаційну властивість лише для подальшого використання. Отже, наша реалізація буде виглядати так:

public final class ClassSingleton { private static ClassSingleton INSTANCE; private String info = "Initial info class"; private ClassSingleton() { } public static ClassSingleton getInstance() { if(INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; } // getters and setters }

Хоча це загальний підхід, важливо зазначити, що він може бути проблематичним у сценаріях багатопотоковості , що є основною причиною використання Singletons.

Простіше кажучи, це може призвести до більш ніж одного випадку, порушуючи основний принцип шаблону. Незважаючи на те, що для цієї проблеми існують блокувальні рішення, наш наступний підхід вирішує ці проблеми на кореневому рівні.

3. Enum Singleton

Рухаючись вперед, не будемо обговорювати ще один цікавий підхід - це використання перелічень:

public enum EnumSingleton { INSTANCE("Initial class info"); private String info; private EnumSingleton(String info) { this.info = info; } public EnumSingleton getInstance() { return INSTANCE; } // getters and setters }

Цей підхід має серіалізацію та безпеку потоків, гарантовану самою реалізацією перерахування, яка забезпечує внутрішню доступність лише одного екземпляра, виправляючи проблеми, зазначені в реалізації на основі класу.

4. Використання

Щоб використовувати наш ClassSingleton , нам просто потрібно отримати екземпляр статично:

ClassSingleton classSingleton1 = ClassSingleton.getInstance(); System.out.println(classSingleton1.getInfo()); //Initial class info ClassSingleton classSingleton2 = ClassSingleton.getInstance(); classSingleton2.setInfo("New class info"); System.out.println(classSingleton1.getInfo()); //New class info System.out.println(classSingleton2.getInfo()); //New class info

Що стосується EnumSingleton , ми можемо використовувати його як будь-який інший Java Enum:

EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance(); System.out.println(enumSingleton1.getInfo()); //Initial enum info EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance(); enumSingleton2.setInfo("New enum info"); System.out.println(enumSingleton1.getInfo()); // New enum info System.out.println(enumSingleton2.getInfo()); // New enum info

5. Загальні підводні камені

Синглтон - це оманливо простий шаблон дизайну, і є кілька типових помилок, які програміст може допустити, створюючи синглтон.

Ми розрізняємо два типи проблем з одиночними:

  • екзистенційний (нам потрібен синглтон?)
  • реалізаційний (чи правильно ми його впроваджуємо?)

5.1. Екзистенційні питання

Концептуально синглтон є різновидом глобальної змінної. Загалом, ми знаємо, що слід уникати глобальних змінних - особливо, якщо їх стани змінюються.

Ми не говоримо, що ми ніколи не повинні використовувати синглтон. Однак ми говоримо, що можуть бути більш ефективні способи організації нашого коду.

Якщо реалізація методу залежить від одиночного об'єкта, чому б не передати його як параметр? У цьому випадку ми чітко покажемо, від чого залежить метод. Як наслідок, ми можемо легко знущатись над цими залежностями (якщо це необхідно) під час проведення тестування.

Наприклад, одиночні часто використовуються для охоплення даних конфігурації програми (тобто підключення до сховища). Якщо їх використовувати як глобальні об'єкти, стає важко вибрати конфігурацію для тестового середовища.

Тому, коли ми запускаємо тести, виробнича база даних псується даними тестів, що навряд чи є прийнятним.

Якщо нам потрібен синглтон, ми можемо розглянути можливість делегування його екземпляра іншому класу - свого роду фабриці -, який повинен подбати про те, щоб у грі був лише один екземпляр сингтона.

5.2. Реалізаційні питання

Незважаючи на те, що одинаки здаються досить простими, їх реалізація може страждати від різних проблем. Усе це призводить до того, що в підсумку ми можемо мати не лише один екземпляр класу.

Синхронізація

Реалізація з приватним конструктором, яку ми представили вище, не є безпечною для потоку: вона добре працює в однопотоковому середовищі, але в багатопотоковому, ми повинні використовувати техніку синхронізації, щоб гарантувати атомність операції:

public synchronized static ClassSingleton getInstance() { if (INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; }

Зверніть увагу на ключове слово, синхронізоване в декларації методу. Тіло методу має кілька операцій (порівняння, створення і повернення).

За відсутності синхронізації існує ймовірність того, що два потоки чергують свої виконання таким чином, що вираз INSTANCE == null отримує значення true для обох потоків, і, як результат, створюються два екземпляри ClassSingleton .

Синхронізація може суттєво вплинути на продуктивність. Якщо цей код часто викликається, нам слід пришвидшити його, використовуючи різні прийоми, такі як ледача ініціалізація або перевірена блокування (майте на увазі, що це може не працювати належним чином через оптимізацію компілятора). Більше деталей ми можемо побачити у нашому навчальному посібнику «Подвійне перевірене блокування за допомогою Singleton».

Кілька екземплярів

Є кілька інших проблем із синглтонами, пов’язаними із самою JVM, які можуть призвести до того, що в результаті ми отримаємо кілька екземплярів синглтона. Ці питання досить тонкі, і ми дамо короткий опис кожного з них:

  1. Синглтон повинен бути унікальним для JVM. Це може бути проблемою для розподілених систем або систем, внутрішні органи яких базуються на розподілених технологіях.
  2. Кожен навантажувач класу може завантажити свою версію синглтона.
  3. Синглтон може бути зібраний сміття, як тільки на нього ніхто не посилається. Ця проблема не призводить до наявності одночасно кількох екземплярів-одиниць, але при відтворенні екземпляр може відрізнятися від попередньої версії.

6. Висновок

У цьому короткому навчальному посібнику ми зосередилися на тому, як реалізувати шаблон Singleton, використовуючи лише основну Java, і як переконатися, що він узгоджується, і як використовувати ці реалізації.

Повну реалізацію цих прикладів можна знайти на GitHub.