Jeśli moja strona Ci pomogła, i chcesz aby była bardziej rozwijana, wesprzyj mnie
buy me a coffee
I. Wzorce kreacyjne
II. Wzorce strukturalne
III. Wzorce czynnościowe
|
Singleton - wzorzec projektowy (design pattern) - java
1. Cel:
Wzorzec projektowy Singleton ma za zadanie dostarczyć tylko jedną instancje klasy.
Zapewnia 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:
package pl.edu.java.singleton;
public final class SingletonNonThreadSafe {
private static SingletonNonThreadSafe instance;
private SingletonNonThreadSafe() {
}
public static SingletonNonThreadSafe getInstance() {
if (instance == null) {
instance = new SingletonNonThreadSafe();
}
return instance;
}
}
b) gorliwe ładowanie (Eagar loading), jest bezpieczne dla wielu wątków
package pl.edu.java.singleton;
public final class SingletonEager {
private static final SingletonEager instance = new SingletonEager();
private SingletonEager(){}
public static SingletonEager getInstance(){
return instance;
}
}
c) synchronizowana metoda
package pl.edu.java.singleton;
public class SingletonSynchMethod {
private static SingletonSynchMethod instance;
private SingletonSynchMethod(){}
public static synchronized SingletonSynchMethod getInstance(){
if(instance == null){
instance = new SingletonSynchMethod();
}
return instance;
}
}
d) synchronizowany blok
package pl.edu.java.singleton;
public class SingletonSynchBlock {
private static SingletonSynchBlock instance;
public static SingletonSynchBlock getInstance(){
if(instance == null){
synchronized (SingletonSynchBlock.class) {
if(instance == null){
instance = new SingletonSynchBlock();
}
}
}
return instance;
}
}
e) ale pomimo tego że mamy blok synchronizowany zmienna instance może się zmienić w linii 11, a nie zostać zauważona w linii 8.
Aby temu zapobiec dodajemy na zmienną instance słowo kluczowe volatile - mówi ono:
"Uważaj - bo może ona zmienić wartość nawet wtedy, gdy się nie spodziewasz!"
to zapobiegnie że nie zostanie zauważona zmiana w jednym wątku gdy zmiana dokonuje się w drugim.
Technicznie rzecz biorąc, zmienna będzie pobierana z pamięci komputera nie z cache-u procesora
package pl.edu.java.singleton;
public class SingletonSynchBlockVolatile {
private static volatile SingletonSynchBlockVolatile instance;
public static SingletonSynchBlockVolatile getInstance(){
if(instance == null){
synchronized (SingletonSynchBlockVolatile.class) {
if(instance == null){
instance = new SingletonSynchBlockVolatile();
}
}
}
return instance;
}
}
f) 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();
package pl.edu.java.singleton;
public class SingletonBillPugh {
private SingletonBillPugh(){}
private static class SingletonHelper{
private static final SingletonBillPugh INSTANCE = new SingletonBillPugh();
}
public static SingletonBillPugh getInstance(){
return SingletonHelper.INSTANCE;
}
}
g) Ale ciągle jest możliwe jest tworzenie instancji przez reflekcje:
public class SingletonRef {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
Class clazz = Class.forName("pl.org.java.singleton.Singleton");
Constrictor<Singleton> cs = clazz.getDeclaredConstructor();
cs.setAccessable(true);
Singleton s3 = cs.newInstance();
print("s1", s1);
print("s2", s2);
print("s3", s3);
}
private static void print(String name, Singleton s) {
System.out.println(String.format("Instancja o nazwie: %s, posiada hashcode: ", name, s.hashcode()));
}
}
Aby temu zapobiec możemy oddac w konstruktorze:
private Singleton() {
if(instance != null) {
throw new RuntimeException("Instance exist! You can't create second!");
}
}
h) Serializacja/deserializacja: wynikają z niej problemy z inną wersją instancji
Klasa testowa
package pl.edu.java.singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SingletonSerializableTest {
public static void main(String[] args) throws Exception {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c:\\matmad\\obj1"));
oos.writeObject(s2);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c:\\matmad\\obj1"));
Singleton s3 = (Singleton)ois.readObject();
print("s1", s1);
print("s2", s2);
print("s3", s3);
}
private static void print(String name, Singleton s) {
System.out.println(String.format("Instancja o nazwie: %s, posiada hashcode: %d", name, s.hashCode()));
}
}
Rozwiązaniem tego jest metoda w klasie singletonu:
privateObjectreadResolve() {
return instance;
}
Klasa singleton-u będzie wyglądać następująco
package pl.edu.java.singleton;
import java.io.Serializable;
class Singleton implements Serializable {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
private ObjectreadResolve() {
return instance;
}
Wada tego rozwiązania jest taka, że wywołanie deserializacji nie nadpisze naszego singletonu.
Więc jeżeli będziemy mieć zmienną value = 1 i zapiszemy do pliku, następnie zmienimy value = 2,
następnie deserializujemy obiekt Singleton zapisany do pliku, to zmienna zapisana w pliku czyli value = 2 nie nadpisze
wartości którą zmieniliśy w naszej klasie, czyli pomimo deserializacji value = 1, obiekt dalej będzie równy 2.
i) Zapobieganie clone-owania:
package pl.edu.java.singleton;
public class SingletonClone implements Cloneable {
private static SingletonClone instance = new SingletonClone();
private SingletonClone() {
}
public static SingletonClone getInstance() {
return instance;
}
public Object clone() throws CloneNotSupportedException {
if ( instance != null ) {
throw new CloneNotSupportedException();
}
return super.clone();
}
}
j) Joshua Bloch zaleca tworzenie singletonu na podstawie typu enum:
package pl.edu.java.singleton;
public enum SingletonEnum {
INSTANCE;
int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
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
|