2017-07-27 47 views
9

Próbuję zrozumieć, jak wielowątkowość działa w Javie. Rozumiem różnicę między Volatile i Synchronization.Wątki Java: czy wszystkie zmienne współdzielone powinny być zmienne?

Volatile dotyczy widoczności i nie gwarantuje synchronizacji. Kiedy pracujemy w środowiskach wielowątkowych, każdy wątek tworzy własną kopię na lokalnym buforze podręcznym zmiennej, z którą mają do czynienia. Gdy ta wartość jest aktualizowana, aktualizacja odbywa się najpierw w lokalnej kopii pamięci podręcznej, a nie w rzeczywistej zmiennej. Dlatego też inne wątki są agnostyczne w odniesieniu do wartości, które zmieniają inne wątki. I tu pojawia się volatile. Pola zmienne są natychmiast zapisywane w pamięci głównej, a odczyty pochodzą z pamięci głównej.

Fragment z Thinking In Java -

Synchronizacja także powoduje spłukiwania do pamięci głównej, więc jeśli pole jest całkowicie strzeżona przez zsynchronizowanych metod lub bloków, nie jest konieczne, aby to niestabilne.

Zwykle bezpieczne jest używanie lotnych zamiast zsynchronizowanych jeśli klasa ma tylko jedno zmienne pole. Znowu, twój pierwszy wybór powinien być być używać zsynchronizowanego słowa kluczowego - to jest najbezpieczniejsze podejście, i próbuje zrobić cokolwiek innego jest ryzykowne.

Ale moje pytanie brzmi: jeśli w bloku zsynchronizowanym modyfikowana jest nielotna zmienna dzielona, ​​czy pozostałe wątki zobaczą zaktualizowane dane? (Ponieważ zmienna w pytaniu nieulotne, inne wątki powinien przeczytać nieświeże dane z pamięci podręcznej zamiast głównej pamięci głównej)

Jeżeli odpowiedź na powyższe pytanie jest NO, to mogę stwierdzić, że za każdym razem używam synchronizację, że powinienem upewnić się, że wspólne zmienne muszą być oznaczone jako volatile?

A jeśli odpowiedź brzmi: YES, to czy oznacza to, że zawsze mogę użyć synchronization zamiast oznaczania wspólnych zmiennych to volatile?

p.s: Przed zadaniem tego pytania przeczytałem wiele odpowiedzi na temat StackOverflow i na innych stronach, ale nie mogłem znaleźć odpowiedzi na moje pytanie.

+0

@Sotirios: Jestem przekonany, że moje pytanie nie jest duplikatem to: https://stackoverflow.com/questions/3214938/java-volatile-modifier-and-synchronized-blocks Szukałem czegoś więcej niż różnica między lotnością a synchronizacją. W każdym razie dzięki za link, przeczytam odpowiedzi. –

+0

Co _ różnica? Zapytałeś, czy musisz oznaczyć zmienną jako 'volatile', jeśli tylko uzyskujesz dostęp do niej przez blok' synchronized'. Odpowiedzi w tym pytaniu dotyczą tego. –

Odpowiedz

11

Aby uprościć nieco:

  • volatile zapewnia tylko widoczność: kiedy czytasz zmienną volatile masz dwie gwarancje: (1) można zobaczyć najnowszy zapis do zmiennej, nawet jeśli została przeprowadzona w innym wątek i (2) wszystkie zapisy przed tym zapisaniem są również widoczne.
  • daje widoczność ORAZ atomowość - wątek obserwujący działania wykonane w bloku synchronized z bloku synchronized za pomocą tego samego monitora będzie widział wszystkie lub żaden z nich.

Tak, aby odpowiedzieć na to pytanie, nie, jeśli zmienna jest zapisywane w synchronized bloku, nie trzeba go volatile zaznaczyć, pod warunkiem, że zawsze czytać tę zmienną z synchronized bloku przy użyciu tego samego monitora .


Oto kilka przykładów z lotnymi:

static class TestVolatile { 
    private int i = 0; 
    private volatile int v = 0; 

    void write() { 
    i = 5; 
    v = 7; 
    } 

    void read() { 
    //assuming write was called beforehand 
    print(i); //could be 0 or 5 
    print(v); //must be 7 
    print(i); //must be 5 
    } 

    void increment() { 
    i = i + 1; //if two threads call the method concurrently 
       //i could be incremented by 1 only, not 2: no atomicity 
    } 
} 

i kilka przykładów z synchronized:

static class TestSynchronized { 
    private int i = 0; 
    private int j = 0; 

    void write() { 
    synchronized(this) { 
     i = 5; 
     j = 7; 
    } 
    } 

    void read_OK() { 
    synchronized(this) { 
     //assuming write was called beforehand 
     print(i); //must be 5 
     print(j); //must be 7 
     print(i); //must be 5 
    } 
    } 

    void read_NOT_OK() { 
    synchronized(new Object()) { //not the same monitor 
     //assuming write was called beforehand 
     print(i); //can be 0 or 5 
     print(j); //can be 0 or 7 
    }   
    } 

    void increment() { 
    synchronized(this) { 
     i = i + 1; //atomicity guarantees that if two threads call the method 
       //concurrently, i will be incremented twice 
    } 
    } 
} 
+0

Więc z twojej odpowiedzi rozumiem, że odczyt w bloku synchronizacji zawsze dzieje się z pamięci głównej. Ale trzeba uważać, aby zapewnić, że nieulotny odczyt nastąpi w bloku synchronizacji. Dlatego warto byłoby oznaczać takie zmienne współdzielone na wypadek gdyby ktoś wykonał odczyt z bloku niezsynchronizowanego. –

+1

@RahulDevMishra Niezupełnie - zazwyczaj masz dwie możliwości: (1) uczynić synchronizację wewnętrznym szczegółem implementacji i nie przeciekać zmiennych - w ten sposób zewnętrzny kod może wykorzystać twoją klasę bez martwienia się lub (2) dokumentować, czego zewnętrzny klient potrzebuje wykonaj - zobacz na przykład: https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedList-java.util.List- – assylias

0

Jeśli zmienna jest chroniony przez tego samego monitora zablokować za każdym razem to dostępnego wtedy nie ma potrzeby, aby było to niestabilne.

Zsynchronizowane bloki robią dwie rzeczy: dostęp do pojedynczych wątków do regionów chronionych przez blokadę i efekty widoczności. Efekty widoczności oznaczają, że wszelkie zmiany dokonane w tej zmiennej, gdy są chronione przez tę blokadę, będą widoczne dla każdego innego wątku, który wejdzie do regionu, który z niego korzysta (blokada).

6

JLS definiuje relację o nazwie "happen-before" na instrukcje w programie. Krótką wersję można zobaczyć in the documentation of java.util.concurrent.

Operacja zapisu do zmiennej jest postrzegana przez operację odczytu tej samej zmiennej, jeśli zapis "stanie się przed" odczytem.

Teraz, jeśli oba wątki uzyskują dostęp do tej zmiennej tylko wewnątrz bloku synchronizacji, wyjście z bloku synchronizacji gwarantuje, że to, co się w nim zdarzyło "zdarza się przed" wszystko, co dzieje się po następnej kłódce tego samego monitora synchronizacji.

Więc jeśli nawlec zapisuje do zmiennej xwewnątrz bloku zsynchronizowanego i gwint B odczytuje z tego xwewnątrz zsynchronizowanym bloku na tym samym monitorze, następnie x nie musi być lotna - write " stało się przed "odczyt i jego wynik będzie widoczny dla wątku B.

Ale jeśli wątek B odczytuje zmienną bez synchronizacji, to nawet jeśli wątek A wykonał synchronizację wewnątrz, nie ma gwarancji, że zapis" wydarzy się wcześniej " , a zmienna jest niebezpieczna - chyba że jest to volatile.

Jeśli więc upewnisz się, że cały dostęp - zarówno do odczytu, jak i zapisu - znajduje się w blokach synchronizacji na tym samym monitorze, możesz polegać na relacji "dzieje się wcześniej", aby twój tekst był widoczny.

+1

Jedną rzeczą do zrozumienia jest to, że chociaż masz rację z punktu widzenia dokumentacji, rzeczywistość jest taka, że ​​bariery pamięci są _nie na monitor. To znaczy. jeśli wątek A opuszcza blok "zsynchronizowany", a następnie wątek B wchodzi do innego bloku 'zsynchronizowanego', wówczas wątek B zobaczy aktualizacje wykonane przez wątek A. Nie ma gwarancji porządku i mogą się nakładać, ale jeśli dzieje się to w tej kolejności następnie B zobaczy aktualizacje. – Gray