2011-01-23 9 views
5

Odnosząc się do http://www.javamex.com/tutorials/synchronization_volatile.shtml, nie jestem pewien, czy muszę użyć słowa kluczowego volatile w następującym przypadku: z powodu dodatkowej reguły 3.Czy muszę używać lotnych, jeśli 2 różne nicki zapisu i odczytu nigdy nie będą żyły w tym samym czasie?

  1. prymitywny zmienna statyczna będzie pisać wątku A.
  2. tej samej zmiennej statycznej prymitywny zostanie odczytany przez Thread B.
  3. wątek B będzie działać tylko po wątku A jest „martwy ". ("martwy" oznacza, że ​​ostatnie stwierdzenie końca pustej nici A zostało zakończone)

Czy nowa wartość zapisana w wątku A zawsze zostanie przypisana do pamięci głównej, po "martwej"? Jeśli tak, czy to oznacza, że ​​nie potrzebuję słowa kluczowego, jeśli powyższe 3 warunki są spełnione?

Mam wątpliwości, czy w tym przypadku wymagana jest volatile. Ponieważ jest to wymagane, może zostać zerwana ArrayList. Jako jeden wątek można wykonać wstawianie i aktualizowanie zmiennej składowej size. Później inny wątek (nie współbieżnie) może odczytać 's . Jeśli spojrzysz na kod źródłowy ArrayList, size nie jest zadeklarowany jako niestabilny.

W JavaDoc z ArrayList, to tylko wspomnieć, że ArrayList nie jest bezpieczny do stosowania na wiele wątków dostęp ArrayList instancji jednocześnie, ale nie dla wiele wątków dostęp ArrayList instancji w różnych terminów.

Pozwól mi użyć następującego kodu do issulate ten problem

public static void main(String[] args) throws InterruptedException { 
    // Create and start the thread 
    final ArrayList<String> list = new ArrayList<String>(); 
    Thread writeThread = new Thread(new Runnable() { 
     public void run() { 
      list.add("hello"); 
     } 
    }); 
    writeThread.join(); 
    Thread readThread = new Thread(new Runnable() { 
     public void run() { 
      // Does it guarantee that list.size will always return 1, as this list 
      // is manipulated by different thread? 
      // Take note that, within implementation of ArrayList, member 
      // variable size is not marked as volatile. 
      assert(1 == list.size()); 
     } 
    }); 
    readThread.join(); 
} 
+2

Oczywistym pytaniem byłoby, dlaczego ktoś użył 2 wątków? Tylko akademicki? –

Odpowiedz

6

Tak, trzeba jeszcze używać lotnych (lub jakąś inną formę synchronizacji).

Powód, dla którego dwa wątki mogły działać na różnych procesorach i nawet jeśli jeden wątek zakończył się na długo przed rozpoczęciem drugiego, nie ma gwarancji, że drugi wątek uzyska najświeższą wartość podczas odczytu. Jeśli pole nie jest oznaczone jako niestabilne i nie jest używana żadna inna synchronizacja, wówczas drugi wątek mógłby uzyskać wartość, która została buforowana lokalnie na procesorze, na którym działa. Ta zbuforowana wartość może teoretycznie być nieaktualna przez długi czas, w tym po zakończeniu pierwszego wątku.

Jeśli użyjesz parametru volatile, wartość będzie zawsze zapisywana i odczytywana z pamięci głównej, z pominięciem buforowanej wartości procesora.

+0

Zsynchronizowane i niestabilne nie są jedynymi sposobami przekazywania danych między wątkami, zobacz moją odpowiedź poniżej. – jtahlborn

+0

Tak, wiem ... ale kiedy czytam to pytanie, Yan Cheng CHEOK chce wiedzieć, jak bardzo ulotne są prace. Przeformułowałem moją odpowiedź, aby uściślić, że konkretnie mówię o niestabilności. –

+0

Wątpię w potrzebę niestabilności, zobacz moje zaktualizowane pytanie. –

1

Możliwe, chyba że ręcznie utworzysz barierę pamięci. Jeśli A ustawi zmienną, a B zdecyduje się podjąć z jakiegoś rejestru, masz problem. Potrzebujesz więc bariery milimonialnej, albo niejawnej (blokującej, lotnej), albo jawnej.

-1

Jeśli Wątek Zdecydowanie umiera przed Wątek B rozpoczyna czytanie to byłoby możliwe, aby uniknąć używania lotny

np.

public class MyClass { 

    volatile int x = 0; 

    public static void main(String[] args) { 

     final int i = x; 
     new Thread() { 
     int j = i; 
     public void run() { 
      j = 10; 
      final int k = j; 
      new Thread() { 
       public void run() { 
        MyClass.x = k; 
       }    
      }.start(); 
     } 
     }.start(); 
    } 
} 

Jednak problemem jest to, że w zależności od tego wątku rozpoczyna wątek B będzie musiał teraz, że wartość, że wątek A pisząc do zmieniła i nie używać własną wersję pamięci podręcznej. Najłatwiej to zrobić, aby wątek A odrodził wątek B.Ale jeśli wątek A nie ma nic więcej do zrobienia, gdy pojawia się wątek B, to wydaje się to trochę bezsensowne (dlaczego nie po prostu użyć tej samej nici).

Inną alternatywą jest to, że jeśli żaden inny wątek nie jest zależny od tej zmiennej, to może wątek A może zainicjować zmienną lokalną zmienną ulotną, zrobić to, co musi, a następnie w końcu zapisać zawartość swojej zmiennej lokalnej z powrotem do zmiennej lotnej. Następnie, gdy rozpoczyna się wątek B, inicjalizuje swoją zmienną lokalną ze zmiennej lotnej i odczytywa tylko z jego lokalnej zmiennej. To powinno znacznie zmniejszyć ilość czasu spędzanego na utrzymywaniu synchronicznej zmiennej lotnej. Jeśli to rozwiązanie wydaje się nie do przyjęcia (z powodu innych wątków zapisujących do zmiennej lotnej lub czegoś podobnego), zdecydowanie musisz zadeklarować zmienną zmienną.

3

Nie, może nie być potrzebna. mimo że odpowiedź Marka Byersa zaczyna się dość precyzyjnie, jest ograniczona. zsynchronizowane i niestabilne nie są tylko sposobami prawidłowego przekazywania danych między wątkami. są inne, mniej omawiane "punkty synchronizacji". w szczególności początek i koniec nici są punktami synchronizacji. jednakże wątek, który rozpoczyna wątek B, musi rozpoznać, że wątek A jest zakończony (na przykład przez połączenie gwintu lub sprawdzenie stanu nici). jeśli tak jest, zmienna nie musi być niestabilna.

+0

Wątpię w potrzebę niestabilności, zobacz moje zaktualizowane pytanie. –

+0

@Yan - nie ma znaczenia, czy dostęp do wątku jest w tym samym czasie, czy też nie. dostęp przez wątki bez prawidłowej synchronizacji jest zepsuty. jednak, jak już wspomniałem, możesz mieć prawidłowe punkty synchronizacji z zakończeniem wątku/początku wątku, jeśli spełnia on wymienione kryteria. – jtahlborn

+0

@Yan - Twój przykładowy kod spełnia wymienione kryteria, więc nie potrzebujesz niestabilności. – jtahlborn

1

http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.4.4

ostatecznego działania w T1 gwintu synchronizacji, z jakiegokolwiek działania w drugiego T2 gwint, który wykrywa, że ​​T1 został zakończony. T2 może wykonać to przez wywołanie T1.isAlive() lub T1.join().

Można więc osiągnąć swój cel bez użycia lotności.

W wielu przypadkach, gdy istnieją oczywiste zależności czasowe, synchronizacja jest wykonywana przez kogoś pod maską, a aplikacja nie wymaga dodatkowej synchronizacji. Niestety nie jest to regułą, programiści muszą dokładnie przeanalizować każdą sprawę.

Jednym z przykładów jest Swing worker thread. Ludzie wykonaliby pewne obliczenia w wątku roboczym, zapisali wynik do zmiennej, a następnie podnieśli wydarzenie. Wątek zdarzenia odczytuje wynik obliczenia ze zmiennej. Żadna jawna synchronizacja nie jest wymagana z kodu aplikacji, ponieważ "podniesienie zdarzenia" już dokonało synchronizacji, więc zapis z wątku roboczego jest widoczny z wątku zdarzenia.

Z jednej strony jest to rozkosz. Z drugiej strony, wielu ludzi nie rozumiało tego, pomijają synchronizację tylko dlatego, że nigdy nie myśleli o tym problemie. Ich programy są poprawne ... tym razem.

Powiązane problemy