2012-06-15 10 views
13

Na drugi dzień Howard Lewis Ship napisali blogu wpis o nazwie "Things I Learned at Hacker Bed and Breakfast", jeden z wypunktowania jest:leniwe inicjowanie bez synchronizacji lub lotnych hasła

Java instancja pola, który jest przypisany dokładnie jeden raz za leniwe inicjalizacji robi nie musi być zsynchronizowany ani niestabilny (tak długo, jak można zaakceptować warunki wyścigu przez wątki, aby przypisać je do pola ); to jest od Rich Hickey

Na pierwszy rzut oka wydaje się to sprzeczne z przyjętą mądrości na temat widoczności zmian w pamięci całej nici, a jeśli jest to ujęte w Java współbieżności w książce praktyki lub w języku Java spróbuj więc tęskniłem. Ale to było coś, co HLS otrzymał od Richa Hickeya podczas wydarzenia, w którym obecny był Brian Goetz, więc wydaje się, że musi w tym być coś. Czy ktoś mógłby wyjaśnić logikę tego stwierdzenia?

+0

nie obawiaj się lotnych odczytów. Inicjalizacja klasy, tzn. modyfikowalny kod jest jedynym przenośnym sposobem, aby zrobić to bez lotności. Instrukcja jest niepoprawna w obliczu architektury procesora, która umożliwia zmianę kolejności zapisów.Na x86 i Sparc TSO volatile read jest darmowy, więc nie ma sensu grać hakerem. – bestsss

Odpowiedz

9

To zdanie brzmi trochę tajemniczo. Jednak myślę, że HLS odnosi się do przypadku, gdy leniwie inicjujesz pole instancji i nie obchodzi cię, że kilka wątków wykonuje tę inicjalizację więcej niż raz.
Jako przykład mogę wskazać metody hashCode() z String Klasa:

private int hashCode; 

public int hashCode() { 
    int hash = hashCode; 
    if (hash == 0) { 
     if (count == 0) { 
      return 0; 
     } 
     final int end = count + offset; 
     final char[] chars = value; 
     for (int i = offset; i < end; ++i) { 
      hash = 31*hash + chars[i]; 
     } 
     hashCode = hash; 
    } 
    return hash; 
} 

Jak widać dostęp do pola hashCode (która posiada buforowane wartość komputerowej hash String) nie jest zsynchronizowany i pola nie jest zadeklarowany jako volatile. Każdy wątek, który wywołuje metodę hashCode(), będzie nadal otrzymywać tę samą wartość, chociaż pole hashCode może być zapisane więcej niż jeden raz przez różne wątki.

Ta technika ma ograniczoną użyteczność. IMHO jest użyteczne głównie w przypadkach takich, jak w przykładzie: buforowany obiekt pierwotny/niezmienny, który jest obliczany z pozostałych ostatecznych/niezmiennych pól, ale jego obliczenie w konstruktorze jest przesadą.

5

Edit:

Hrm. Kiedy to czytam, jest to technicznie niepoprawne, ale w praktyce z pewnymi zastrzeżeniami. Tylko ostatnie pola można bezpiecznie zainicjować raz i uzyskać dostęp do wielu wątków bez synchronizacji.

Lazy zainicjowane wątki mogą cierpieć z powodu problemów z synchronizacją na wiele sposobów. Na przykład, możesz mieć warunki wyścigu konstruktorów, w których odniesienie do klasy zostało wyeksportowane bez sama klasa jest w pełni zainicjalizowana.

Myślę, że to zależy od tego, czy masz prymitywne pole, czy obiekt. Pierwotne pola, które można zainicjować wiele razy, jeśli nie przeszkadza ci to, że wiele wątków sprawdzi się poprawnie. Jednak inicjalizacja stylu w ten sposób może być problematyczna. Nawet wartości long na niektórych architekturach mogą przechowywać różne słowa w wielu operacjach, więc można wyeksportować połowę wartości, chociaż podejrzewam, że long nigdy nie przekroczyłoby strony pamięci, a więc nigdy by się to nie wydarzyło.

myślę, że to zależy w dużym stopniu od tego, czy aplikacja ma żadnych bariera pamięci - wszelkie synchronized bloki lub dostępu do volatile dziedzinach. Diabeł tkwi w szczegółach tutaj, a kod, który wykonuje leniwą inicjalizację, może działać dobrze na jednej architekturze z jednym zestawem kodu, a nie w innym modelu gwintu lub z aplikacją, która rzadko synchronizuje.


Oto dobry kawałek na polach końcowych jako porównania:

http://www.javamex.com/tutorials/synchronization_final.shtml

Jako Java 5, jeden szczególności wykorzystanie końcowego słów kluczowych jest bardzo ważny i często pomijany broń w twoja zbrojownia współbieżności. Zasadniczo, ostateczny może być użyty, aby upewnić się, że podczas konstruowania obiektu inny wątek uzyskujący dostęp do tego obiektu nie widzi tego obiektu w stanie częściowo skonstruowanym, jak mogłoby się zdarzyć w innym przypadku.Dzieje się tak dlatego, że w przypadku użycia jako atrybutu zmiennych obiektu, ostateczna ma następującą ważną cechę jako część jego definicji:

Teraz, nawet jeśli pole jest oznaczone jako ostateczne, jeśli jest klasą, może modyfikować pola w klasie. To jest inny problem i nadal musisz mieć synchronizację.

5

To działa dobrze w pewnych warunkach.

  • To jest w porządku, aby spróbować ustawić pole więcej niż jeden raz.
  • jest to w porządku, jeśli poszczególne wątki mają różne wartości.

Często podczas tworzenia obiektu, który nie został zmieniony, np. ładowanie właściwości z dysku, posiadającego więcej niż jedną kopię przez krótki czas, nie jest problemem.

private static Properties prop = null; 

public static Properties getProperties() { 
    if (prop == null) { 
     prop = new Properties(); 
     try { 
      prop.load(new FileReader("my.properties")); 
     } catch (IOException e) { 
      throw new AssertionError(e); 
     } 
    } 
    return prop; 
} 

W krótkim okresie jest to mniej efektywne niż blokowanie, ale w dłuższej perspektywie może być bardziej wydajne. (Chociaż Właściwości ma własny zamek, ale masz pomysł;)

IMHO, to nie jest rozwiązanie, które działa we wszystkich przypadkach.

Być może chodzi o to, że w niektórych przypadkach można użyć bardziej zrelaksowanych technik spójności pamięci.

+2

To jednak powoduje problemy z kondycją wyścigu konstruktora. Możesz uzyskać odwołanie do obiektu wyeksportowanego do innego wątku bez pełnego inicjowania obiektu. – Gray

+0

@ Moja wiedza na temat leniwego inicjowania jest taka, że ​​zawsze jest inicjalizowana zgodnie z wymaganiami. Nie powinno być widać niezainicjowanej wartości, ale możesz spróbować ustawić ją więcej niż raz. –

+0

Problem, ponieważ widzę go @Peter jest, jeśli nie ma synchronizacji, brak bariery pamięci, to istnieje możliwość części obiektu jest współdzielony między pamięciami podręcznymi bez aktualizacji pamięci _full_ obiektu. Jeśli wątek A działający na innym procesie miał swoją własną kopię strony nr 1, ale nie strony nr 2, a obiekt, który miał miejsce na każdej stronie, został leniwy zainicjalizowany przez wątek B, wówczas wątek A może uzyskać częściowo zainicjowany obiekt. – Gray

3

Myślę, że oświadczenie jest nieprawdziwe. Inny wątek może zobaczyć częściowo zainicjowany obiekt, więc referencja może być widoczna dla innego wątku, nawet jeśli konstruktor nie został jeszcze uruchomiony. Jest to omówione w praktyce Współbieżność Java w sekcji 3.5.1:

public class Holder { 

    private int n; 

    public Holder (int n) { this.n = n; } 

    public void assertSanity() { 
     if (n != n) 
      throw new AssertionError("This statement is false."); 
    } 

} 

Ta klasa nie jest bezpieczna dla wątków.

Jeśli widoczny obiekt to niezmienny, to jestem w porządku, ponieważ semantyka pól końcowych oznacza, że ​​nie zobaczysz ich, dopóki jego konstruktor nie zakończy pracy (sekcja 3.5.2).

+0

+1 w celach informacyjnych :-) – dacwe

+0

@dacwe Żądam mojego upomnika za wpisanie tego wszystkiego :-) – artbristol