2011-11-05 10 views
8

Mam kawałek kodu, który wygląda tak:Czy zmienne zmienne Java narzucają relację, która miała miejsce przed odczytaniem?

Fragment A:

class Creature { 
    private static long numCreated; 
    public Creature() { 
     synchronized (Creature.class) { 
      numCreated++; 
     } 
    } 
    public static long numCreated() { 
     return numCreated; 
    } 
} 

Z mojego zrozumienia, ponieważ odczyt numCreated nie jest zsynchronizowany, jeśli wątek-A tworzy Creature na 1pm , a Thread-B czyta numCreated() o godzinie 14.00, numCreated() może również powrócić 0 lub 1 (nawet gdy wątek-A zakończył inicjowanie obiektu o godzinie 13:15).

Więc dodałem synchronized do numCreated():

Fragment B:

class Creature { 
    private static long numCreated; 
    public Creature() { 
     synchronized (Creature.class) { 
      numCreated++; 
     } 
    } 
    public static synchronized long numCreated() { // add "synchronized" 
     return numCreated; 
    } 
} 

i wszystko jest dobrze, poza tym, że myślałem, gdybym go zmodyfikować, aby Snippet C jest zmienna numCreated nadal poprawnie synchronizowane?

Fragment C:

class Creature { 
    private static volatile long numCreated; // add "volatile" 
    public Creature() { 
     synchronized (Creature.class) { 
      numCreated++; 
     } 
    } 
    public static long numCreated() { // remove "synchronized" 
     return numCreated; 
    } 
} 

Z Snippet C, jest to gwarancją, że jak tylko wątek-A kończy tworzenie obiektów w 1:05 pm, call thread-B do numCreated() pewnością powrót 1?

PS: Rozumiem, że w realnej sytuacji to pewnie użyć AtomicLong ale to jest dla celów uczenia

+0

Lotne odczyty są tanie, to jest to, co powinieneś wiedzieć. Jednak nie są tanie. Synchronizacja wymaga zarówno zmiennego zapisu, jak i CAS (porównaj i ustaw), a także monitorowanych poręczycieli dla OS, co jest motywacją do zablokowania impl. – bestsss

+0

biorąc pod uwagę poprzedni komentarz, nie próbuj grać * inteligentniej * przez usunięcie volatile. – bestsss

+0

@bestsss Nie rozumiem twojego ostatniego komentarza, co masz na myśli, nie usuwając volatile? (w Snippet C dodawałem niestabilności i usuwam zsynchronizowane) – Pacerier

Odpowiedz

6

Zobacz http://download.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility:

Zapis do pola lotnej dzieje, przed każdym kolejne przeczytanie tego samego pola. Zapis i odczytywanie zmiennych pól ma podobne efekty spójności pamięci, co wejście i wyjście monitorów, ale nie powoduje wzajemnego blokowania wykluczania.

Odpowiedź brzmi "tak". Zapis zmiennej w konstruktorze następuje przed odczytem zmiennej w numCreated(). A ponieważ przyrost nieatomowy jest nadal wykonywany w zsynchronizowanym bloku, synchronizacja jest w porządku (inkrementacja nie jest atomowa, ale zapis zmiennej lotnej jest).

+0

Dziękuję za pomoc = D Czy mógłbyś skomentować wydajność używania volatile (Snippet C) w porównaniu do wydajności używania synchronizacji do czytania (Snippet B)? Innymi słowy, czy wolisz B lub C? – Pacerier

+0

Wolałbym AtomicLong, ponieważ ułatwia synchronizację i punkt jest oczywisty. Bez ATomicLong wolałbym fragment B, ponieważ jest łatwiejszy do zrozumienia i nie miesza dwóch strategii synchronizacji. Różnica w wydajności powinna być nieistotna w 99% przypadków. Wielowątkowość jest bardzo trudna, nie jest często wybierana przez większość programistów i nie jest dobrze znana. W związku z tym dbałbym o czytelność i poprawność przed dbaniem o wydajność. –

+0

Wydajność obu jest podobna, ponieważ poprawka do modelu pamięci Java (wersja?) –