2010-03-24 28 views
5

Używam jody ze względu na jej dobrą opinię na temat wielowątkowości. Daje duże odległości, aby wielowątkowe przetwarzanie danych było wydajne, na przykład dzięki niezmienności wszystkich obiektów Date/Time/DateTime.Częściowo skonstruowany obiekt/wielowątkowość

Ale tutaj jest sytuacja, w której nie jestem pewien, czy Joda naprawdę robi dobrze. Prawdopodobnie tak, ale jestem bardzo zainteresowany, aby zobaczyć wyjaśnienie.

Kiedy toString() z DateTime jest nazywany Joda wykonuje następujące operacje:

/* org.joda.time.base.AbstractInstant */ 
public String toString() { 
    return ISODateTimeFormat.dateTime().print(this); 
} 

Wszystkie formatujących są bezpieczne dla wątków (one niezmienne, jak również), ale co o formatyzatora-przetwórni:

private static DateTimeFormatter dt; 

/* org.joda.time.format.ISODateTimeFormat */ 
public static DateTimeFormatter dateTime() { 
    if (dt == null) { 
     dt = new DateTimeFormatterBuilder() 
      .append(date()) 
      .append(tTime()) 
      .toFormatter(); 
    } 
    return dt; 
} 

Jest to popularny wzór w aplikacjach z jednym gwintem, ale wiadomo, że jest podatny na błędy w środowisku wielowątkowym.

widzę następujące zagrożenia: stan

  • Race podczas sprawdzania null -> najgorszy przypadek: dwa przedmioty dostać utworzony.

Nie ma problemu, ponieważ jest to wyłącznie obiekt pomocniczy (w przeciwieństwie do normalnej sytuacji pojedynczego wzoru), jeden zostaje zapisany w dt, drugi zostaje utracony i prędzej czy później zostanie zebrany śmieci.

  • zmienna statyczna może wskazywać na częściowo wybudowanego obiektu przed objec została zakończona inicjalizacja

(przed wywołaniem mnie do szału, przeczytaj o podobnej sytuacji w tym Wikipedia article.)

Tak w jaki sposób Joda zapewnia, że ​​żaden częściowo utworzony formatter nie zostanie opublikowany w tej zmiennej statycznej?

Dzięki za wyjaśnienia!

Reto

Odpowiedz

4

Powiedziałeś, że formatery są tylko do odczytu. Jeśli używają tylko pól końcowych (nie czytałem źródła formatowania), to w trzeciej edycji specyfikacji języka Java są chronione przed częściowym tworzeniem obiektów przez "Final Field Semantics". Nie sprawdziłem 2-tej edycji JSL i nie jestem pewien, czy taka inicjalizacja jest poprawna w tej edycji.

Patrz rozdział 17.5 i 17.5.1 w JLS. Stworzę "łańcuch zdarzeń" dla wymaganej relacji przed zdarzeniem.

Po pierwsze, gdzieś w konstruktorze zapisuje się do końcowego pola w formatyzatorze. To jest zapis w. Po zakończeniu konstruktora wykonano akcję "zamrożenia". Nazwijmy to f. Gdzieś później w porządku programu (po powrocie z konstruktora, może innych metodach i powrocie z toFormattera) jest zapis do pola dt. Dajmy temu napisać imię a. Ten zapis (a) następuje po operacji zamrożenia (f) w "kolejności programu" (kolejność w wykonaniu jednowątkowym), a zatem f dzieje się przed (hb (f, a)) po prostu według definicji JLS. Uff, inicjalizacja zakończona ... :)

Nieco później, w innym wątku, występuje wywołanie do formatu dateTime(). W tym czasie potrzebujemy dwóch czytań. Pierwszy z dwóch odczytuje ostatnią zmienną w obiekcie formatyzatora. Nazwijmy to r2 (aby być spójnym z JLS). Drugi z nich to odczyt "tego" dla Formattera. Dzieje się to podczas wywoływania metody dateTime(), gdy odczytuje się pole dt. I nazwijmy to przeczytaj r1. Co mamy teraz? Przeczytaj, jak zobaczyłem r1, piszę do dt. Uważam, że ten zapis był akcją z poprzedniego akapitu (tylko jeden wątek napisał to pole, tylko dla uproszczenia). Jako r1 patrz zapis a, to mc (a, r1) (relacja "Łańcuch pamięci", definicja pierwszego zdania). Aktualny wątek nie zainicjował formatera, odczytuje go w polu r2 i widzi "adres" formatyzatora odczytanego podczas akcji r1. Tak więc, z definicji, istnieją dereferencje (r1, r2) (kolejna akcja zamawiająca z JLS).

Mamy zapis przed zamrożeniem, hb (w, f). Zamrożenie przed przypisaniem dt, hb (f, a). Mamy odczyt z dt, mc (a, r1). I mamy łańcuch dereferencji między r1 i r2, dereferencjami (r1, r2). Wszystko to prowadzi do relacji hb (w, r2) dzieje się przedtem tylko przez definicję JLS. Również, z definicji, hb (d, w), gdzie d jest zapisem wartości domyślnej dla końcowego pola w obiekcie. Tak więc, odczyt r2 nie widzi zapisu w i musi widzieć zapis r2 (jedyny zapis do pola z kodu programu).

To samo dotyczy bardziej pośredniego dostępu do pola (końcowe pole obiektu przechowywane w polu końcowym, itp.).

Ale to nie wszystko! Nie ma dostępu do częściowo zbudowanego obiektu. Ale jest bardziej interesujący błąd. W przypadku braku wyraźnej synonimizacji dateTime() może zwrócić wartość null. Nie sądzę, aby takie zachowanie można było zaobserwować w praktyce, ale wydanie JLS 3 nie zapobiega takiemu zachowaniu. Najpierw odczytany z pola dt w metodzie może zobaczyć wartość zainicjowaną przez inny wątek, ale drugi odczyt z dt może zobaczyć "zapis wartości defalut". Nie dzieje się - zanim istnieją relacje, aby temu zapobiec. Takie możliwe zachowanie jest specyficzne dla wydania trzeciego, drugie wydanie ma "zapis do pamięci głównej"/"odczyt z pamięci głównej", który nie pozwala wątkowi zobaczyć wartości zmiennej cofającej się w czasie.

+0

+1. to kolejny powód, dla którego obiekty niezmienne są "prostsze" w równoległym modelu Java. Jednak nie jestem pewien o swoim ostatnim akapicie, patrz JLS 17.4.4: „Wpisanie wartości domyślnej (zero, fałszywe lub null), aby każda zmienna synchronizuje-z pierwszej akcji w każdym wątku”. Konieczne jest, aby "zapis wartości domyślnej" nastąpił przed odczytaniem dt. – irreputable

+0

Tak, zapis wartości domyślnej jest przed odczytem dt. Zapobiega to wątkom odczytywania wartości "śmieci". Ale zapis pola w innym wątku (który zainicjował pole dt) nie jest w relacji hb z odczytanym dt. W ten sposób możemy zobaczyć dowolny z tych dwóch zapisów (domyślnie lub z wątku, który zainicjował dt) w dowolnym momencie. Jedynym momentem, w którym musimy odczytać domyślną wartość inicjalizacji, jest sprawdzenie wymaganej ilości. Ale w tym czasie możemy popełnić tylko jeden odczyt o wartości domyślnej i na następnej iteracji powiedzieć, że wątek zobaczyć zapis obiektu DT (tylko dla pierwszego odczytu w metodzie). – maxkar

-1

IMO najgorszy przypadek to nie dwa obiekty coraz utworzona, ale kilka (tyle ile istnieją wątki zawijające dateTime(), aby być dokładne). Ponieważ dateTime() nie jest zsynchronizowany i dt nie jest ani ostateczny, ani niestabilny, zmiana jego wartości w jednym wątku nie jest gwarantowana dla innych wątków. Więc nawet po tym, jak jeden wątek zainicjował dt, dowolna liczba innych wątków może nadal widzieć odwołanie jako wartość NULL, więc z przyjemnością tworzy nowe obiekty.

Poza tym, jak wyjaśniono przez innych, częściowo utworzony obiekt nie może zostać opublikowany przez dateTime(). Nie można również częściowo zmienić (= zwisające) odniesienia, ponieważ aktualizacje wartości odniesienia są gwarantowane jako atomowe.

0

To jest trochę nie-odpowiedź, ale najprostszym wytłumaczeniem

Więc w jaki sposób upewnić się, że nie Joda częściowo utworzony formater zostanie opublikowana w tej zmiennej statycznej?

może to być po prostu to, że niczego nie zapewniają, a deweloperzy nie zdawali sobie sprawy, że może to być błąd lub że nie warto się z nim synchronizować.

0

I I asked a similar question na liście dyskusyjnej Joda w 2007 roku, choć nie znalazłem odpowiedzi na rozstrzygające, a ja w efekcie unikałem czasu Jody, na lepsze lub na gorsze.

Wersja 3 specyfikacji języka Java gwarantuje, że aktualizacje informacji o obiektach są atomowe, niezależnie od tego, czy są one 32-bitowe czy 64-bitowe.To, w połączeniu z argumentami określonymi powyżej, powoduje, że kod Joda gwintu bezpieczny MOM (patrz java.sun.com/docs/books/jls/third_edition/html/memory.html#17.7)

IIRC, wersja 2 JLS nie zawierają ten sam wyraźny wyjaśnienie dotyczące referencji obiektu, czyli tylko 32-bitowe bibl były gwarantowane atomowej, więc jeśli były przy użyciu 64 bitowej JVM nie ma gwarancji, że to działa. W tym czasie korzystałem z wersji Java 1.4, która wcześniej zawierała wersję JLS v3.

Powiązane problemy