Одинак — це породжуючий патерн, який гарантує існування тільки одного об’єкта певного класу, а також дозволяє дістатися цього об’єкта з будь-якого місця програми.
Одинак має такі ж переваги та недоліки, що і глобальні змінні. Його неймовірно зручно використовувати, але він порушує модульність вашого коду.
Ви не зможете просто взяти і використовувати клас, залежний від одинака, в іншій програмі. Для цього доведеться емулювати там присутність одинака. Найчастіше ця проблема проявляється при написанні юніт-тестів.
Ознаки застосування патерна: Одинака можна визначити за статичним створюючим методом, який повертає один і той же об’єкт.
Наївний Одинак (один потік)
Незграбно реалізувати Одинака дуже просто — достатньо приховати конструктор і надати створюючий статичний метод.
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) { // The following code emulates slow initialization. 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) { // The following code emulates slow initialization. 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 { // The field must be declared volatile so that double check lock would work // correctly. private static volatile Singleton instance; public String value; private Singleton(String value) { this.value = value; } public static Singleton getInstance(String value) { // The approach taken here is called double-checked locking (DCL). It // exists to prevent race condition between multiple threads that may // attempt to get singleton instance at the same time, creating separate // instances as a result. // // It may seem that having the `result` variable here is completely // pointless. There is, however, a very important caveat when // implementing double-checked locking in Java, which is solved by // introducing this local variable. // // You can read more info DCL issues in Java here: // https://refactoring.guru/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. Якщо цікаво, можете ознайомитися з ними тут: