2013-08-07 14 views
41

Oto moja klasa niestandardowa dla wzorca singleton. w tym kodzie używam podwójnie sprawdzanego blokowania, jak poniżej. Ponieważ czytam wiele postów na jakimś źródle, mówią, że podwójne sprawdzanie jest użyteczne, ponieważ zapobiega uruchomieniu dwóch współbieżnych wątków w tym samym czasie, tworząc dwa różne obiekty.Double Checked Locking w Singleton

public class DoubleCheckLocking { 

    public static class SearchBox { 
     private static volatile SearchBox searchBox; 

     // private attribute of this class 
     private String searchWord = ""; 
     private String[] list = new String[]{"Stack", "Overflow"}; 

     // private constructor 
     private SearchBox() {} 

     // static method to get instance 
     public static SearchBox getInstance() { 
      if (searchBox == null) { // first time lock 
       synchronized (SearchBox.class) { 
        if (searchBox == null) { // second time lock 
         searchBox = new SearchBox(); 
        } 
       } 
      } 
      return searchBox; 
     } 
} 

Nadal nie rozumiem powyższego kodu tak bardzo. Na czym polega problem, jeśli dwa wątki razem uruchamiają ten sam wiersz kodu, gdy instancja ma wartość null?

if (searchBox == null) { 
       synchronized (SearchBox.class) { 
        if (searchBox == null) { 
         searchBox = new SearchBox(); 
        } 
       } 
      } 

Kiedy to się pojawi. oba dwa wątki zobaczą obiekt o wartości NULL. następnie obydwie synchronizują. a następnie, sprawdzają ponownie i nadal widzą ją null. i utworzyć dwa różne obiekty. OOOPS.

Proszę wyjaśnij mi. Co rozumiem źle?

Dzięki :)

+0

Dokładnie to samo pytanie [tutaj] (https://stackoverflow.com/q/12316406/465053) dla świata C#. – RBT

Odpowiedz

42

Nie, ponieważ jesteś uzyskania blokady na SearchBox.class, tylko jeden wątek wejdzie zsynchronizowany blok na raz. Tak więc pierwszy wątek wchodzi następnie znajduje searchBox i tworzy go, a następnie opuszcza zsynchronizowany blok, następnie drugi wątek wchodzi do bloku, a następnie stwierdza, że ​​searchBox nie jest pusty, ponieważ pierwszy wątek już go utworzył, więc nie utworzy nowego wystąpienia searchBox

Wzorzec sprawdzany podwójnie służy do uniknięcia pobrania blokady za każdym razem, gdy kod jest wykonywany, jeśli połączenie nie odbywa się razem, wówczas pierwszy warunek nie powiedzie się, a wykonanie kodu nie spowoduje wykonania blokady, co pozwoli zaoszczędzić zasoby.

+5

Nie wiem zbyt wiele na ten temat, ale najwyraźniej ten rodzaj podwójnie sprawdzanego zamka jest zepsuty. Nie działa w taki sposób, jak można się tego spodziewać (zgodnie z tym) (http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) –

+0

@WilliamMorrison dzięki ... nie widziałem że przed ... przechodzeniem przez to teraz –

+21

faktycznie, ten przykład jest _nie_ zepsuty. w modelu pamięci jdk 1.5+, sprawienie, by wartość odniesienia była niestabilna (jak w kodzie OP), "naprawia" podwójnie sprawdzany wzór blokowania. – jtahlborn

6

Ten podwójny zamek kontrolny jest potrzebny tylko wtedy, gdy martwisz się wieloma wątkami wywołującymi singleton jednocześnie lub kosztem uzyskania blokady w ogóle.

Jego celem jest zapobieganie niepotrzebnej synchronizacji, a tym samym szybkie przechowywanie kodu w środowisku wielowątkowym.

Check out this link for more information.

Jeśli używasz w Java 1.5 lub większym, i użyć volatile słowa kluczowego w double-check zablokowanego mechanizmu, to będzie działać dobrze. Ponieważ używasz słowa kluczowego volatile, Twój przykład nie jest uszkodzony zgodnie z tym samym linkiem powyżej. wygląd

+2

Downvoter, rozum? To jest dobra odpowiedź. –

20

Miejmy w tym kodzie:

1 if (searchBox == null) { 
2  synchronized (SearchBox.class) { 
3  if (searchBox == null) { 
4   searchBox = new SearchBox(); 
5  } 
6 } 

Spróbujmy względu na ten temat. Załóżmy, że mamy dwa wątki: A i B i załóżmy, że przynajmniej jeden z nich osiągnie linię 3 i zauważy, że searchBox == null jest true. Dwa wątki nie mogą być jednocześnie na linii 3 z powodu bloku synchronized. To jest klucz , aby zrozumieć, dlaczego działa mechanizm podwójnego sprawdzania. Tak więc musi się zdarzyć, że albo A lub B najpierw przejdzie przez synchronized. Bez utraty ogólności powiedz, że ten wątek to A. Następnie, po zobaczeniu, że searchBox == null ma wartość true, wchodzi w treść instrukcji i ustawia searchBox na nowe wystąpienie SearchBox. Następnie ostatecznie opuści blok synchronized.Teraz będzie kolej na B, aby wejść: pamiętaj, B został zablokowany czekając, aż A wyjdzie. Teraz, gdy wejdzie do bloku, będzie obserwować searchBox. Ale A będzie po lewej stronie po ustawieniu searchBox na wartość inną niż null. Gotowe.

Nawiasem mówiąc, w Javie najlepszym sposobem na wdrożenie singletonu jest użycie pojedynczego elementu typu enum. Od Effective Java:

Chociaż takie podejście nie zostało jeszcze powszechnie przyjęte, jednoelementowy typ wyliczeniowy jest najlepszym sposobem na wdrożenie singletonu.

+0

faktycznie, wersja OP _does_ pracy (zgodnie z artykułem, który łączyłeś). – jtahlborn

+1

To nie zadziała. Powiedzmy, że wątek C przechodzi do linii 1, podczas gdy wątek A wykonywał linię 4, wątek C może widzieć częściowo skonstruowany obiekt. Zgodnie z JMM, tylko jeśli dwa wątki blokują ten sam obiekt, mają gwarancję, że zobaczą tę samą wersję. Synchronizacja z pamięcią główną może nastąpić przed wyjściem z bloku zsynchronizowanego. – deeKay

+0

to zdecydowanie najlepsza odpowiedź na to pytanie. dzięki! przegłosowane – takeradi

1
if (searchBox == null) { //1 
    synchronized (SearchBox.class) { 
     if (searchBox == null) { //2 
      searchBox = new SearchBox(); 
      } 
     } 
    } 
} 
  1. Jeżeli instancja została już utworzona, nic nie robić - unikać gwinty Blokady mechanizmów
  2. pierwszy wątek, który nabył kontroli blokady i widzi, że nie ma takiego obiektu i tworzy go . Zwalnia blokadę, a drugi może zrobić to samo - musi sprawdzić, czy obiekt istnieje, ponieważ pierwszy mógł go utworzyć.

Więc w zasadzie zewnętrzna if jest stosowany w celu zapobiegania nadmiarowych zamki - to wszystko pozwala wątek wiedzieć, że nie jest już przedmiotem i nie trzeba zablokować/nic robić. Wewnętrzny if służy do tego, aby współbieżny wątek wiedział, czy inny już utworzył obiekt, czy nie.