1. Singleton
1. Cel:
Wzorzec projektwoy Singleton ma za zadanie dostarczyć tylko jedną instancje klasy.
Zapenia jeden globalny punkt dostępu do tej instancji.
2. Problem:
Singleton używany jest na przykład gdy chcemy zapewnić kontrole dostępu do dzielonego zasobu takiego jak baza danych lub plik itp.


3. Rozwiązanie:

Prywatny konstruktor który zapobiega użyciu słowa kluczowego new, dla instancji klasy.
Stworzenie metody statycznej zwracającej stworzony tylko jeden obiekt.

4. Diagram klas klasy Singleton:

5. Implementacja:
Mamy kilka rodzajów implementacji singleton-a.

a) Jednowątkowy:
  1. package pl.edu.java.singleton;
  2.  
  3. public final class SingletonNonThreadSafe {
  4.     private static SingletonNonThreadSafe instance;
  5.  
  6.     private SingletonNonThreadSafe() {
  7.     }
  8.  
  9.     public static SingletonNonThreadSafe getInstance() {
  10.         if (instance == null) {
  11.             instance = new SingletonNonThreadSafe();
  12.         }
  13.         return instance;
  14.     }
  15. }
  16.  
b) gorliwe ładowanie (Eagar loading), jest bezpieczne dla wielu wątków
  1. package pl.edu.java.singleton;
  2.  
  3. public final class SingletonEager {
  4.    
  5.     private static final SingletonEager instance = new SingletonEager();
  6.    
  7.     private SingletonEager(){}
  8.  
  9.     public static SingletonEager getInstance(){
  10.         return instance;
  11.     }
  12. }
  13.  
c) synchronizowana metoda
  1. package pl.edu.java.singleton;
  2.  
  3. public class SingletonSynchMethod {
  4.  
  5.     private static SingletonSynchMethod instance;
  6.    
  7.     private SingletonSynchMethod(){}
  8.    
  9.     public static synchronized SingletonSynchMethod getInstance(){
  10.         if(instance == null){
  11.             instance = new SingletonSynchMethod();
  12.         }
  13.         return instance;
  14.     }
  15.    
  16. }
  17.  
d) synchronizowany blok
  1. package pl.edu.java.singleton;
  2.  
  3. public class SingletonSynchBlock {
  4.  
  5.     private static SingletonSynchBlock instance;
  6.    
  7.         public static Singleton getInstance(){
  8.             if(instance == null){
  9.                 synchronized (SingletonSynchBlock.class) {
  10.                     if(instance == null){
  11.                         instance = new SingletonSynchBlock();
  12.                         }
  13.                     }
  14.             }
  15.             return instance;
  16.        }
  17.   }
e) Singleton Bill-a Pugh-a
Wydaje się najlepszym podejściem, ponieważ nie jest ładowana do pamięci do póki nie zostanie wywołana metoda statyczne getInstance();
  1. package pl.edu.java.singleton;
  2.  
  3. public class SingletonBillPugh {
  4.  
  5.     private SingletonBillPugh(){}
  6.    
  7.     private static class SingletonHelper{
  8.         private static final SingletonBillPugh INSTANCE = new SingletonBillPugh();
  9.     }
  10.    
  11.     public static SingletonBillPugh getInstance(){
  12.         return SingletonHelper.INSTANCE;
  13.     }
  14. }
  15.  
f) Ale ciągle jest możliwe jest tworzenie instancji przez reflekcje:
  1. public class SingletonRef {
  2.     public static void main(String[] args) {
  3.        Singleton s1 = Singleton.getInstance();
  4.        Singleton s2 = Singleton.getInstance();
  5.        Class clazz = Class.forName("pl.org.java.singleton.Singleton");
  6.        Constrictor<Singleton> cs = clazz.getDeclaredConstructor();
  7.        cs.setAccessable(true);
  8.        Singleton s3 = cs.newInstance();
  9.        print("s1", s1);
  10.        print("s2", s2);
  11.        print("s3", s3);
  12.     }
  13.        
  14.     private static void print(String name, Singleton s) {
  15.             System.out.println(String.format("Instancja o nazwie: %s, posiada hashcode: ", name, s.hashcode()));
  16.     }
  17. }
  18.  
Aby temu zapobiec możemy oddac w konstruktorze:
  1.         private Singleton() {
  2.                 if(instance != null) {
  3.                         throw new RuntimeException("Instance exist! You can't create second!");
  4.                 }
  5.         }

g) Serializacja/deserializacja: wynikają z niej problemy z inną wersją instancji
Klasa testowa
  1. package pl.edu.java.singleton;
  2.  
  3. import java.io.FileInputStream;
  4. import java.io.FileOutputStream;
  5. import java.io.ObjectInputStream;
  6. import java.io.ObjectOutputStream;
  7.  
  8. public class SingletonSerializableTest {
  9.     public static void main(String[] args) throws Exception {
  10.         Singleton s1 = Singleton.getInstance();
  11.         Singleton s2 = Singleton.getInstance();
  12.  
  13.         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c:\\matmad\\obj1"));
  14.         oos.writeObject(s2);
  15.         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c:\\matmad\\obj1"));
  16.  
  17.         Singleton s3 = (Singleton)ois.readObject();
  18.  
  19.         print("s1", s1);
  20.         print("s2", s2);
  21.         print("s3", s3);
  22.  
  23.     }
  24.     private static void print(String name, Singleton s) {
  25.         System.out.println(String.format("Instancja o nazwie: %s, posiada hashcode: %d", name, s.hashCode()));
  26.     }
  27. }
Rozwiązaniem tego jest metoda w klasie singletonu:
  1. private Object readResolve() {
  2.         return instance;
  3. }
Klasa singleton-u będzie wyglądać następująco
  1. package pl.edu.java.singleton;
  2.  
  3. import java.io.Serializable;
  4.  
  5. class Singleton implements Serializable {
  6.     private static Singleton instance = new Singleton();
  7.     private Singleton() {
  8.     }
  9.  
  10.     public static Singleton getInstance() {
  11.         return instance;
  12.     }
  13.     private Object readResolve() {
  14.         return instance;
  15.     }
  16.  
Wadą tego rozwiązania jest taka że wywołanie deserializacji nie nadpisze naszego singletonu
Więc jeżeli będziemy mieć zmienną 'value', zrobimy setter i getter, ustawimy value = 1, zapiszemy do pliku
zmienimy value = 2, deserializujemy obiekt Singleton, zmienna zostanie ta po zmianie czyli value = 2, a nie
ta którą deserializowaliśmy czyli value = 1 (ta została zapisana do pliku i deserializowana).

h) Zapobieganie clone-owania:
  1. package pl.edu.java.singleton;
  2. public class SingletonClone implements Cloneable {
  3.     private static SingletonClone instance = new SingletonClone();
  4.     private SingletonClone() {
  5.     }
  6.  
  7.     public static SingletonClone getInstance() {
  8.         return instance;
  9.     }
  10.         public Object clone() throws CloneNotSupportedException {
  11.                 if ( instance != null ) {
  12.                         throw new CloneNotSupportedException();
  13.                 }
  14.                 return super.clone();
  15.         }
  16. }
i) Joshua Bloch zaleca tworzenie singletonu na podstawie typu enum:
  1. package pl.edu.java.singleton;
  2.  
  3. public enum SingletonEnum {
  4.     INSTANCE;
  5.  
  6.     int value;
  7.  
  8.     public int getValue() {
  9.         return value;
  10.     }
  11.     public void setValue(int value) {
  12.         this.value = value;
  13.     }
  14. }
  15.  
6. Zastosowanie w kodzie java:

java.lang.Runtime#getRuntime() - zastowowany SingletonEager:
java.lang.System#getSecurityManager() - zastosowana metoda synchronizowana SingletonSynchMethod,
ale wewnątrz innej metody. Zmienna ze słowem kluczowym volatile;
java.awt.Desktop#getDesktop() - zastosowana metoda synchronizowana SingletonSynchMethod
created by freelancer.java.org.pl © 2020