Одиночка — это порождающий паттерн, который гарантирует существование только одного объекта определённого класса, а также позволяет достучаться до этого объекта из любого места программы.
Одиночка имеет такие же преимущества и недостатки, что и глобальные переменные. Его невероятно удобно использовать, но он нарушает модульность вашего кода.
Вы не сможете просто взять и использовать класс, зависящий от одиночки в другой программе. Для этого придётся эмулировать присутствие одиночки и там. Чаще всего эта проблема проявляется при написании юнит-тестов.
Признаки применения паттерна: Одиночку можно определить по статическому создающему методу, который возвращает один и тот же объект.
Наивный Одиночка (один поток)
Топорно реализовать Одиночку очень просто — достаточно скрыть конструктор и предоставить статический создающий метод.
Singleton.java: Одиночка
package refactoring_guru.singleton.example.non_thread_safe; public final class Singleton { private static Singleton instance; public String value; private Singleton(String value) { // Этот код эмулирует медленную инициализацию. try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } this.value = value; } public static Singleton getInstance(String value) { if (instance == null) { instance = new Singleton(value); } return instance; } }
DemoSingleThread.java: Клиентский код
package refactoring_guru.singleton.example.non_thread_safe; public class DemoSingleThread { public static void main(String[] args) { System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" + "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" + "RESULT:" + "\n"); Singleton singleton = Singleton.getInstance("FOO"); Singleton anotherSingleton = Singleton.getInstance("BAR"); System.out.println(singleton.value); System.out.println(anotherSingleton.value); } }
OutputDemoSingleThread.txt: Результаты выполнения
If you see the same value, then singleton was reused (yay!) If you see different values, then 2 singletons were created (booo!!) RESULT: FOO FOO
Наивный Одиночка (много потоков)
Тот же класс ведёт себя неправильно в многопоточной среде. Несколько потоков могут одновременно вызвать метод получения Одиночки и создать сразу несколько экземпляров объекта.
Singleton.java: Одиночка
package refactoring_guru.singleton.example.non_thread_safe; public final class Singleton { private static Singleton instance; public String value; private Singleton(String value) { // Этот код эмулирует медленную инициализацию. try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } this.value = value; } public static Singleton getInstance(String value) { if (instance == null) { instance = new Singleton(value); } return instance; } }
DemoMultiThread.java: Клиентский код
package refactoring_guru.singleton.example.non_thread_safe; public class DemoMultiThread { public static void main(String[] args) { System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" + "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" + "RESULT:" + "\n"); Thread threadFoo = new Thread(new ThreadFoo()); Thread threadBar = new Thread(new ThreadBar()); threadFoo.start(); threadBar.start(); } static class ThreadFoo implements Runnable { @Override public void run() { Singleton singleton = Singleton.getInstance("FOO"); System.out.println(singleton.value); } } static class ThreadBar implements Runnable { @Override public void run() { Singleton singleton = Singleton.getInstance("BAR"); System.out.println(singleton.value); } } }
OutputDemoMultiThread.txt: Результаты выполнения
If you see the same value, then singleton was reused (yay!) If you see different values, then 2 singletons were created (booo!!) RESULT: FOO BAR
Многопоточный Одиночка
Чтобы исправить проблему, требуется синхронизировать потоки при создании объекта-Одиночки.
Singleton.java: Одиночка
package refactoring_guru.singleton.example.thread_safe; public final class Singleton { // Поле обязательно должно быть объявлено volatile, чтобы двойная проверка // блокировки сработала как надо. private static volatile Singleton instance; public String value; private Singleton(String value) { this.value = value; } public static Singleton getInstance(String value) { // Техника, которую мы здесь применяем называется «блокировка с двойной // проверкой» (Double-Checked Locking). Она применяется, чтобы // предотвратить создание нескольких объектов-одиночек, если метод будет // вызван из нескольких потоков одновременно. // // Хотя переменная `result` вполне оправданно кажется здесь лишней, она // помогает избежать подводных камней реализации DCL в Java. // // Больше об этой проблеме можно почитать здесь: // https://refactoring.guru/ru/java-dcl-issue Singleton result = instance; if (result != null) { return result; } synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(value); } return instance; } } }
DemoMultiThread.java: Клиентский код
package refactoring_guru.singleton.example.thread_safe; public class DemoMultiThread { public static void main(String[] args) { System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" + "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" + "RESULT:" + "\n"); Thread threadFoo = new Thread(new ThreadFoo()); Thread threadBar = new Thread(new ThreadBar()); threadFoo.start(); threadBar.start(); } static class ThreadFoo implements Runnable { @Override public void run() { Singleton singleton = Singleton.getInstance("FOO"); System.out.println(singleton.value); } } static class ThreadBar implements Runnable { @Override public void run() { Singleton singleton = Singleton.getInstance("BAR"); System.out.println(singleton.value); } } }
OutputDemoMultiThread.txt: Результаты выполнения
If you see the same value, then singleton was reused (yay!) If you see different values, then 2 singletons were created (booo!!) RESULT: BAR BAR
Хотите ещё?
Существует ещё с полдюжины способов реализации Одиночки в Java. Если интересно, можете ознакомиться с ними здесь: