2016-07-07 12 views
9

Wiem, że można bezpiecznie opublikować obiekt bezpieczny dla wątków, pisząc odniesienie do pola final lub volatile, które później zostanie odczytane dokładnie przez jeden inny wątek, pod warunkiem, że po opublikowaniu, wątek który stworzył obiekt odrzuca odniesienie do niego, dzięki czemu nie może już zakłócać lub nieświadomie obserwować użycie obiektu w innym wątku.Bezpieczna publikacja lokalnych ostatecznych odniesień

Ale w tym przykładzie nie ma wyraźnych zmiennych lokalnych final, tylko final. Jeśli osoba dzwoniąca odrzuca jej odniesienie do unsafe, czy ta bezpieczna publikacja jest bezpieczna?

void publish(final Unsafe unsafe) { 
    mExecutor.execute(new Runnable() { 
     public void run() { 
      // do something with unsafe 
     } 
    } 
} 

znalazłem kilka Q & AS, jak this one, sugerując, że final zmienne lokalne są niejawnie "skopiowane" do anonimowych klas. Czy to oznacza, że ​​powyższy przykład jest równoważny z tym?

void publish(final Unsafe unsafe) { 
    mExecutor.execute(new Runnable() { 
     final Unsafe mUnsafe = unsafe; 
     public void run() { 
      // do something with mUnsafe 
     } 
    } 
} 

Edycja dla wyjaśnienia:

Unsafe może być cokolwiek, ale mówią, że jest coś takiego:

public class Unsafe { 
    public int x; 
} 

I mExecutor jest coś, co spełnia umowę Executor.

+1

Twój executor używa kolejki wątków bezpiecznych. Aby zobaczyć ten problem, musisz przekazać obiekt między wątkami bez użycia odpowiednich barier pamięci w dowolnym miejscu. –

+1

@PeterLawrey 'class Executor {void execute (Runnable r) {}}' - brak kolejki tutaj. Ale kwestia jest * prawdopodobnie * ważna, mimo to ... – Marco13

+0

BTW 'Unsafe' to klasa, która ma singleton, chociaż możesz stworzyć ich więcej ... –

Odpowiedz

4

Chociaż, nie jestem całkiem pewien, że mam rzeczywisty punkt twojego pytania, i (jak wskazano w komentarzach) problem prawdopodobnie nie jest naprawdę problemem w twoim konkretnym przypadku, może odpowiednie spostrzeżenia można uzyskać z testu/przykład

Biorąc pod uwagę następujące klasy:

import java.util.concurrent.ExecutorService; 

class Unsafe 
{ 

} 

class SafePublication 
{ 
    private final ExecutorService mExecutor = null; 

    public void publish(final Unsafe unsafe) 
    { 
     mExecutor.execute(new Runnable() 
     { 
      @Override 
      public void run() 
      { 
       // do something with unsafe 
       System.out.println(unsafe); 
      } 
     }); 
    } 
} 

można go skompilować i uzyskać dwa .class pliki:

  • SafePublication.class
  • SafePublication$1.class do wewnętrznej klasy

Dekodowanie plik klasy do wewnętrznej klasy daje następujące:

class SafePublication$1 implements java.lang.Runnable { 
    final Unsafe val$unsafe; 

    final SafePublication this$0; 

    SafePublication$1(SafePublication, Unsafe); 
    Code: 
     0: aload_0 
     1: aload_1 
     2: putfield  #1     // Field this$0:LSafePublication; 
     5: aload_0 
     6: aload_2 
     7: putfield  #2     // Field val$unsafe:LUnsafe; 
     10: aload_0 
     11: invokespecial #3     // Method java/lang/Object."<init>":()V 
     14: return 

    public void run(); 
    Code: 
     0: getstatic  #4     // Field java/lang/System.out:Ljava/io/PrintStream; 
     3: aload_0 
     4: getfield  #2     // Field val$unsafe:LUnsafe; 
     7: invokevirtual #5     // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 
     10: return 
} 

Można zobaczyć, że dla parametru final, rzeczywiście istnieje pole wprowadzone w tej klasie. To pole to val$unsafe i jest to final field in the class file sense i jest inicjowane w konstruktorze.

(To nie jest w pełni równoznaczne z drugim opublikowanym fragmentem kodu, ponieważ drugi zawiera dwa ostatnie pola, a oba są inicjowane z tą samą wartością., Ale w odniesieniu do kwestii bezpiecznej publikacji, efekt powinno być takie samo).

+0

To jest dokładnie to, do czego dążyłem. –

1

Pytanie wydaje się być częściowo odpowiedział tej odpowiedzi:

Java multi-threading & Safe Publication

przynajmniej jeśli chodzi o „bezpiecznej publikacji”.

Teraz, jeśli wywołujący odrzuci swoje odwołanie, zmienna będzie bezpieczna, ponieważ nie istnieje odwołanie do zmiennej z wyjątkiem końcowej zmiennej lokalnej.

Co do przykładów kodu - w moich oczach oba fragmenty kodu są równoważne. Wprowadzenie dodatkowej zmiennej lokalnej nie zmienia semantyki, w obu przypadkach kompilator rozpoznaje odwołanie jako niezmienne i pozwala z nim przejść.

EDIT - Wyjeżdżam tej części udokumentować moje błędnej interpretacji kwestii PO za

Dla wyjaśnienia - Biorę wykorzystanie final lub volatile jak przyznano w tym przykładzie, więc właściwe wyżywienie bariera pamięci dla widoczności Odniesienie do obiektu jest tam, jedynym punktem jest możliwa zmienność obiektu, który nie jest bezpieczny dla wątków, którego nie można zagwarantować za pomocą barier pamięciowych i faktycznie nie ma z nimi nic wspólnego. Można to zająć poprzez odpowiednią synchronizację lub pozostawienie tylko jednego odniesienia do treści.

EDIT2 - po przeczytaniu komentarzy PO za

Właśnie spojrzał na JSR 133 FAQ - AFAIU bezpieczne publikacja odniesienia do obiektu przy użyciu bariera pamięci nie gwarantuje, że unsychronized dziedzinach wymienionych odwoływać obiekt jest również widoczny. Ani przez final ani volatile.

jeśli nie jestem misinterpreting to nas tylko Synchronizacja na tym samym monitorze definiuje „zdarza się, zanim” związek na wszystkie się pisze jeden wątek przedtem zwolnieniu blokady synchronizacji i pozyskiwania blokadę na tym samym monitorze przez inny wątek .

Mogę się pomylić, ale brzmi to tak, jakby były również niezsynchronizowane pola obiektu odniesienia.

przypadku korzystania final słowa kluczowego (jak w przykładzie, w którym parametr zostanie wstawiony jako pole final) - tylko pól instancji odwołanie obiektu, które same są final są gwarancją widoczne po wybudowaniu obiektu kończy.

Ale w BlockingQueue (oraz jego implementacji, LinkedBlockingQueue) nie widzę synchronized słowa kluczowego w ogóle - wydaje się, że używa jakiś bardzo mądry kod do wdrożenia synchronizacji za pomocą volatile pól, dla mnie to nie brzmi jak synchronizacja na monitorze w znaczeniu opisanym w JSR 133.

Co oznacza, że ​​zwykłe kolejki blokujące używane przez Executora nie gwarantują widoczności nieostatnich pól instancji Unsafe. Chociaż samo odwołanie może być bezpiecznie opublikowane przy użyciu tylko słowa kluczowego final, bezpieczne publikowanie pól, do których odnosi się ten punkt odniesienia, wymaga również, aby pola były również final lub synchronizacji z monitorem udostępnionym przez program piszący i czytnik.

Nie strzelaj do posłańca :-).

3

Zostawiłeś ważny kod z pierwszego przykładu: Obiekt mExecutor prawdopodobnie jest właścicielem BlockingQueue. Wywołanie mExecutor.execute(r) prawdopodobnie wywoła q.put(r), aby dodać swoje zadanie do kolejki, a później w przyszłości wątek roboczy wywoła r=q.take(), aby uzyskać zadanie, zanim będzie mógł zadzwonić pod numer r.run().

Metody kolejki blokującej utworzą ten sam rodzaj relacji "dzieje się wcześniej" pomiędzy zdarzeniami w dwóch wątkach, które zostaną ustalone za pomocą jednego z idiomów "bezpiecznej publikacji".

Wszystko, co pierwszy wątek aktualizuje w pamięci przed wywołaniem q.put(r) gwarantuje, że stanie się widoczne na sekundę przed zwróceniem połączenia q.take().

+0

Argument, który popełniasz, wydaje mi się tu nie na miejscu: OP mówi wprost o obiektach nieobsługujących wątków, semantyka "dzieje się wcześniej" pomaga ustawić odniesienie, ale nie przeciwko mutowaniu obiektu zmiennego przez osobę wywołującą * po * Runnable ma został umieszczony w kolejce i możliwy dokładnie w tym samym czasie, w którym jest używany w Runnie. W rzeczywistości nie gwarantuje to absolutnie niczego pod względem bezpieczeństwa wątków i bezpiecznej publikacji w odniesieniu do dostępu do danej zmiennej lokalnej. –

+0

@TomaszStańczak, OP zdawał się mówić, że tak się nie stanie, "pod warunkiem, że po opublikowaniu, wątek, który wytworzył obiekt, odrzuca odniesienie do niego, aby nie mógł już zakłócać lub nieświadomie obserwować użycie obiektu w innym wątku . " –

+0

I właśnie to jest gwarancja, a nie "dzieje się przed" relacji 'put' i' get', która sama w sobie nie może dać tej gwarancji. Właściwie już wywołanie funkcji ma tę relację, co się dzieje: 1. wywołać publikację, 2. umieścić, 3. uzyskać. Jeśli oryginalne odwołanie do zmiennej zostanie odrzucone, wystarczy wywołanie 'publish', niezależnie od tego, co dzieje się dalej. –