Java Singleton i synchronizacja


117

Proszę o wyjaśnienie moich pytań dotyczących singletona i wielowątkowości:

  • Jaki jest najlepszy sposób implementacji Singletona w Javie w środowisku wielowątkowym?
  • Co się dzieje, gdy wiele wątków próbuje uzyskać dostęp do getInstance() metody w tym samym czasie?
  • Czy możemy zrobić singletony getInstance() synchronized?
  • Czy synchronizacja jest naprawdę potrzebna podczas korzystania z klas Singleton?

Odpowiedzi:


211

Tak, jest to konieczne. Istnieje kilka metod, których można użyć do osiągnięcia bezpieczeństwa wątków przy leniwej inicjalizacji:

Synchronizacja drakońska:

private static YourObject instance;

public static synchronized YourObject getInstance() {
    if (instance == null) {
        instance = new YourObject();
    }
    return instance;
}

To rozwiązanie wymaga synchronizacji każdego wątku, podczas gdy w rzeczywistości wystarczy tylko kilka pierwszych.

Synchronizacja podwójnego sprawdzenia :

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

To rozwiązanie zapewnia, że ​​tylko kilka pierwszych wątków, które próbują zdobyć twój singleton, musi przejść przez proces pozyskania blokady.

Inicjalizacja na żądanie :

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

To rozwiązanie korzysta z gwarancji modelu pamięci Java dotyczących inicjowania klas, aby zapewnić bezpieczeństwo wątków. Każdą klasę można załadować tylko raz i będzie ona ładowana tylko wtedy, gdy będzie potrzebna. Oznacza to, że pierwszy raz getInstancezostanie wywołany, InstanceHolderzostanie załadowany i instanceutworzony, a ponieważ jest to kontrolowane przez ClassLoaders, nie jest wymagana dodatkowa synchronizacja.


23
Ostrzeżenie - zachowaj ostrożność przy podwójnie sprawdzanej synchronizacji. Nie działa poprawnie z maszynami JVM starszymi niż Java 5 z powodu „problemów” z modelem pamięci.
Stephen C

3
-1 Draconian synchronizationi Double check synchronizationgetInstance () - metoda musi być statyczna!
Ponury

2
@PeterRader Oni nie muszą być static, ale może więcej sensu, jeśli były. Zmieniono zgodnie z wnioskiem.
Jeffrey,

4
Nie ma gwarancji, że implementacja podwójnie sprawdzonego blokowania będzie działać. Faktycznie zostało to wyjaśnione w cytowanym artykule na temat podwójnie sprawdzanego blokowania. :) Jest tam przykład użycia volatile, który działa poprawnie dla 1.5 i nowszych (podwójnie sprawdzone blokowanie jest po prostu zepsute poniżej 1.5). Inicjalizacja na żądanie również cytowana w artykule byłaby prawdopodobnie prostszym rozwiązaniem w Twojej odpowiedzi.
utknąłem

2
@MediumOne AFAIK, rnie jest potrzebne do poprawności. To tylko optymalizacja, aby uniknąć dostępu do zmiennego pola, ponieważ jest to znacznie droższe niż dostęp do zmiennej lokalnej.
Jeffrey,

69

Ten wzorzec wykonuje bezpieczną dla wątków leniwą inicjalizację wystąpienia bez jawnej synchronizacji!

public class MySingleton {

     private static class Loader {
         static final MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return Loader.INSTANCE;
     }
}

Działa, ponieważ używa programu ładującego klasy, aby wykonać całą synchronizację za Ciebie za darmo: klasa MySingleton.Loaderjest najpierw dostępna wewnątrz getInstance()metody, więc Loaderklasa ładuje się, gdy getInstance()jest wywoływana po raz pierwszy. Ponadto program ładujący klasy gwarantuje, że cała inicjalizacja statyczna jest zakończona, zanim uzyskasz dostęp do klasy - to zapewnia bezpieczeństwo wątków.

To jest jak magia.

W rzeczywistości jest bardzo podobny do wzorca wyliczenia w Jhurtado, ale uważam, że wzorzec wyliczenia stanowi nadużycie koncepcji wyliczenia (chociaż działa)


11
Synchronizacja jest nadal obecna, jest po prostu wymuszana przez JVM zamiast przez programistę.
Jeffrey,

@Jeffrey Masz oczywiście rację - pisałem to wszystko (zobacz edycje)
Bohemian

2
Rozumiem, że to nie ma znaczenia dla JVM, mówię tylko, że zrobiło to dla mnie różnicę, jeśli chodzi o samodokumentowany kod. Po prostu nigdy wcześniej nie widziałem wszystkich wielkich liter w Javie bez słowa kluczowego „final” (lub wyliczenie), dostałem trochę dysonansu poznawczego. Dla kogoś, kto programuje Java w pełnym wymiarze godzin, prawdopodobnie nie miałoby to znaczenia, ale jeśli przeskakujesz języki w tę iz powrotem, dobrze jest być wyraźnym. To samo dla początkujących. Chociaż jestem pewien, że można dość szybko przystosować się do tego stylu; wszystkie kapsle chyba wystarczą. Nie chcę nic wybierać, podobał mi się twój post.
Ruby,

1
Doskonała odpowiedź, chociaż nie dostałem jakiejś części. Czy możesz rozwinąć „Co więcej, program ładujący klasy gwarantuje, że cała statyczna inicjalizacja jest zakończona, zanim uzyskasz dostęp do klasy - to właśnie zapewnia bezpieczeństwo wątków”. , jak to pomaga w zabezpieczaniu nici, jestem trochę zdezorientowany.
gaurav jain

2
@ wz366 właściwie, chociaż nie jest to konieczne, zgadzam się ze względów stylowych (ponieważ jest to ostateczne rozwiązanie, ponieważ żaden inny kod nie ma do niego dostępu) finalpowinien zostać dodany. Gotowe.
Bohemian

21

Jeśli pracujesz w środowisku wielowątkowym w Javie i chcesz zagwarantować, że wszystkie te wątki mają dostęp do jednej instancji klasy, możesz użyć Enum. Będzie to miało dodatkową zaletę, ułatwiając obsługę serializacji.

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

a następnie po prostu pozwól swoim wątkom używać Twojej instancji, tak jak:

Singleton.SINGLE.myMethod();

8

Tak, musisz dokonać getInstance()synchronizacji. Jeśli tak nie jest, może dojść do sytuacji, w której można utworzyć wiele instancji klasy.

Rozważ przypadek, w którym masz dwa wątki, które wywołują getInstance()w tym samym czasie. Teraz wyobraź sobie, że T1 wykonuje się tuż po instance == nullsprawdzeniu, a następnie uruchamia się T2. W tym momencie instancja nie jest tworzona ani ustawiana, więc T2 przejdzie kontrolę i utworzy instancję. Teraz wyobraź sobie, że wykonanie przełącza się z powrotem na T1. Teraz singleton jest tworzony, ale T1 już sprawdził! Zacznie ponownie tworzyć obiekt! ZrobieniegetInstance() zsynchronizowane Zapobiega to problem.

Istnieje kilka sposobów na zapewnienie bezpieczeństwa wątków singletonów, ale getInstance()synchronizacja jest prawdopodobnie najprostsza.


Czy to pomoże, umieszczając kod do tworzenia obiektów w zsynchronizowanym bloku, zamiast synchronizacji całej metody?
RickDavis

@RaoG Nie. Chcesz zarówno sprawdzenie, jak i utworzenie w bloku synchronizacji. Potrzebujesz, aby te dwie operacje odbywały się razem bez przerw lub sytuacja, którą opisałem powyżej, może się zdarzyć.
Oleksi

7

Enum singleton

Najprostszym sposobem zaimplementowania Singletona, który jest bezpieczny dla wątków, jest użycie Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Ten kod działa od wprowadzenia Enum w Javie 1.5

Podwójnie sprawdzone ryglowanie

Jeśli chcesz zakodować „klasyczny” singleton, który działa w środowisku wielowątkowym (począwszy od Javy 1.5), powinieneś użyć tego.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class){
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance ;
  }
}

To nie jest bezpieczne dla wątków przed wersją 1.5, ponieważ implementacja słowa kluczowego volatile była inna.

Wczesne ładowanie Singletona (działa nawet przed Java 1.5)

Ta implementacja tworzy wystąpienie singletona, gdy klasa jest ładowana i zapewnia bezpieczeństwo wątków.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}

2

Możesz również użyć bloku kodu statycznego, aby utworzyć wystąpienie wystąpienia podczas ładowania klasy i zapobiec problemom z synchronizacją wątków.

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}

@Vimsha Kilka innych rzeczy. 1. Powinieneś zrobić instanceostatnią 2. Należy dokonać getInstance()statycznego.
John Vint,

Co byś zrobił, gdybyś chciał stworzyć wątek w singletonie.
Arun George,

@ arun-george użyj puli wątków, pojedynczej puli wątków, jeśli to konieczne, i otocz ją chwilą (true) -try-catch-throwable, jeśli chcesz mieć pewność, że twój wątek nigdy nie umrze, bez względu na błąd?
tgkprog

0

Jaki jest najlepszy sposób implementacji Singletona w Javie w środowisku wielowątkowym?

Zapoznaj się z tym postem, aby uzyskać najlepszy sposób implementacji Singletona.

Jaki jest skuteczny sposób implementacji wzorca singleton w Javie?

Co się dzieje, gdy wiele wątków próbuje uzyskać dostęp do metody getInstance () w tym samym czasie?

Zależy to od sposobu, w jaki zaimplementowałeś metodę. Jeśli używasz podwójnego blokowania bez zmiennej lotnej, możesz otrzymać częściowo skonstruowany obiekt Singleton.

Więcej informacji można znaleźć w tym pytaniu:

Dlaczego w tym przykładzie podwójnie sprawdzanego ryglowania użyto dlaczego jest zmienne

Czy możemy zsynchronizować metodę getInstance () singletona?

Czy synchronizacja jest naprawdę potrzebna podczas korzystania z klas Singleton?

Nie jest wymagane, jeśli implementujesz Singleton w poniższe sposoby

  1. statyczna intitalizacja
  2. enum
  3. LazyInitalaization z Initialization-on-demand_holder_idiom

Odnieś się do tego pytania, aby uzyskać więcej informacji

Wzorzec projektowy Java Singleton: pytania


0
public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

Źródło: Efektywna Java -> Punkt 2

Sugeruje użycie go, jeśli masz pewność, że klasa zawsze pozostanie singleton.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.