2013-08-09 20 views
6

Mam serwer trochę tak:Bezpieczny dla wątków, ale szybki dostęp do "ostatecznej" zmiennej?

class Server { 

    private WorkingThing worker; 

    public void init() { 
     runInNewThread({ 
      // this will take about a minute 
      worker = new WorkingThing(); 
     }); 
    } 

    public Response handleRequest(Request req) { 
     if (worker == null) throw new IllegalStateException("Not inited yet"); 
     return worker.work(req); 
    } 

} 

Jak widać, istnieją wątki obsługi żądań i gwint initing serwer. Żądania mogą nadejść przed zakończeniem wnioskowania, dlatego istnieje czek z IllegalStateException.

Teraz, aby uczynić ten wątek bezpiecznym (tak, aby wątki obsługi nie pojawiały się po przeterminowaniu, null, wersja worker, musiałbym uczynić pracownika zmiennym, zsynchronizować go lub taki.

Jednak po zakończeniu init, worker nigdy więcej się nie zmieni, więc jest skuteczny. Dlatego wydaje się, że każda kłótnia o zamek, która może wystąpić, byłaby marnotrawstwem. Więc, co jest najbardziej efektywnego, co mogę tutaj zrobić?

Teraz wiem, że to naprawdę nie ma znaczenia w sensie praktycznym (z całym ciężkim uniesieniem czytania żądania sieci, itp., Co ma wspólnego pojedynczy zamek?), Ale chciałbym wiedzieć z ciekawości .

+0

użycie słowa kluczowego "zsynchronizowane" kosztuje wiele cykli. Myślę, że najlepszym sposobem byłoby uczynienie zmiennej 'worker' zmienną' static'. W ten sposób przydzieli zarezerwowaną przestrzeń w pamięci i myślę, że wątki zawsze będą patrzeć na tę przestrzeń. Tylko myśl, może nie być poprawna. –

+0

Czy wywołanie wątku 'worker.work (req)' jest bezpieczne? Po prostu pytam, czy należy zablokować wywoływanie wątków do czasu zainicjowania 'worker'. Czy to jest poprawne? –

+0

@ViktorSeifert Tak, po uruchomieniu 'worker' jest bezpieczny dla wątków. –

Odpowiedz

3

Notatka na temat lotności: oznaczanie zmiennej zmienności jest tańsze niż używanie synchronizacji (nie wymaga blokad) i jest na ogół wystarczająco tanie, że nie zauważysz. W szczególności na architekturach x86 odczyt zmiennej lotnej nie kosztuje więcej niż odczyt zmiennej nielotnej. Jednak pisanie do zmiennej jest droższe, a fakt, że zmienna jest niestabilna, może zapobiec optymalizacji kompilatora.

Używanie volatile jest prawdopodobnie opcją zapewniającą najlepszy stosunek wydajności do złożoności w scenariuszu.

Nie masz zbyt wielu alternatyw. Ostatecznie sprowadza się to do zapewnienia bezpiecznej publikacji twojego pracownika. I bezpiecznych idiomy publikacji należą:

  • inicjalizacji instancji ze statycznego initialiser
  • znakowanie odwołanie do instancji jako ostatecznego
  • oznakowanie odwołanie do instancji jako lotny
  • synchronizacji wszystkich dostępów

W twoim przypadku dostępne są tylko dwie ostatnie opcje, a użycie metody lotnej jest bardziej efektywne.

+0

Och, myślałem, że lotny był mniej więcej syntaktycznym cukrem dla "nadaj tej zmiennej blokadę i zawsze synchronizuj ją podczas uzyskiwania dostępu do tej zmiennej". Miło wiedzieć, że tak nie jest. –

+0

Należy zauważyć, że inicjalizacja statyczna odbywa się w zsynchronizowanym bloku; w ten sposób gwarantowana jest widoczność (i inne właściwości). – erickson

1

Należy zadeklarować, że pracownik jest volatile.

powodu dwóch powodów

  1. jeśli nie uznaniu jej jako lotny niż ze względu na zmianę kolejności i widoczności efektów Widać było niezerowe odniesienie do niekompletne zbudowane Pracownik obiektu. iw ten sposób można uzyskać niepożądane efekty. Nie stanie się tak, jeśli uczynisz swojego pracownika niezmiennym przy użyciu wszystkich zmiennych końcowych .

  2. Teoretycznie może się zdarzyć, że główny wątek może przez dłuższy czas nie widzieć niezerowej referencji obiektu roboczego. Więc unikaj tego.

Podsumowując, jeśli pracownik jest niezmienny, również w przypadku punktu 2, należy go uaktywnić. Zawsze unikaj nieprzewidywalnych wyników. Zadeklarowanie tego, że jest niestabilna, zajmie się tymi kwestiami.

0

Użyłbym ava.util.concurrent.atomic.AtomicReference , jeśli ma wartość NULL, tzn. Rzuca lub czeka i spróbuj ponownie (jeśli init jest wystarczająco szybki).

1

Zastosowanie prosty zamek do pierwotnego wniosku:

public synchronized void init() { 
    if(worker!=null) return; 
    runInNewThread({ 
     synchronized(Server.this){ 
      worker = new WorkingThing(); 
      Server.this.notify(); 
     } 
    }); 
    this.wait(); 
} 

public Response handleRequest(Request req) { 
    if (worker == null) synchronized(this) { 
     this.wait(); 
    } 
    return worker.work(req); 
} 

To działa, ponieważ istnieją punkty synchronizacji między dostępów do pracownika.

+1

Nigdy nie czekaj poza pętlą. Jeśli przegapisz sygnał powiadomienia, możesz czekać na zawsze. I może być więcej niż jeden wątek waiting => use notifyAll. I nie ma sensu czekać na koniec init. – assylias

+0

Argh, right - powinno być sprawdzane podwójnie w bloku synchronizowanym handleRequest (this). –

Powiązane problemy