2010-10-27 15 views
10

La mia domanda riguarda pubblicazione sicura di valori di campo in Java (come rilevato qui Java multi-threading & Safe Publication).Pubblicazione sicura quando i valori vengono letti in metodi sincronizzati

quanto mi risulta, un campo può essere tranquillamente lettura (ovvero l'accesso da più thread vedrà il valore corretto) se:

  • lettura e scrittura sono sincronizzati sullo stesso monitor
  • campo è finale
  • campo è volatile

Se la mia comprensione è corretta la seguente classe dovrebbe non essere thread-safe dal momento che il il valore iniziale è scritto senza queste caratteristiche. Tuttavia trovo difficile credere che sia necessario effettuare firstvolatile anche se si accede solo da un metodo synchronized.

public class Foo { 

    private boolean needsGreeting = true; 

    public synchronized void greet() { 
     if (needsGreeting) { 
      System.out.println("hello"); 
      needsGreeting = false; 
     } 
    } 
} 

Mi manca qualcosa? Il codice sopra è corretto e se sì perché? Oppure è necessario in questi casi rendere firstvolatile o utilizzare un final AtomicBoolean o qualcosa di simile in aggiunta a per accedervi da un metodo synchronized.

(Giusto per chiarire, mi rendo conto, che se il valore iniziale è stato scritto in un metodo synchronized, sarebbe thread-safe, anche senza la parola chiave volatile.)

risposta

3

A rigor di termini, non riesco a vedere che è sicuro assumere che needsGreeting è impostato su true, quando viene chiamato greet.

Perché questo sia vero, ci dovrebbe essere un evento prima della relazione tra la scrittura iniziale (che si verifica quando l'oggetto è costruito) e la prima lettura (nel metodo greet). Chapter 17 Threads and Locks nel JLS tuttavia, afferma, a proposito accade-prima (HB) vincoli:

17.4.5 Happens-prima di ordine due azioni possono essere ordinati da un accade-prima del rapporto. Se si verifica un'azione, prima di un'altra, la prima è visibile e ordinata prima della seconda.

Se abbiamo due azioni x e y, scriviamo hb (x, y) per indicare che x accade prima di y.

  • Se xey sono azioni dello stesso filo e x y viene prima in ordine di programma, quindi hb (x, y).
  • C'è uno spigolo precedente alla fine di un costruttore di un oggetto all'inizio di un finalizzatore (§12.6) per quell'oggetto.
  • Se un'azione x si sincronizza con una seguente azione y, allora abbiamo anche hb (x, y).
  • Se hb (x, y) ehb (y, z), quindi hb (x, z).

Inoltre, l'unico modo per introdurre un sincronizzato, con rapporto, ovvero un ordine sincronizzazione è di fare qualcosa delle seguenti operazioni:

azioni sincronizzazione inducono la sincronizzate -Con riferimento alle azioni, definite come segue:

  • un'azione sblocco sul monitor m sincronizza-con tutte le azioni di blocco successive su m (laddove successivo viene definito in base all'ordine di sincronizzazione).
  • Una scrittura su una variabile volatile (§8.3.1.4) v si sincronizza con tutte le letture successive di v da qualsiasi thread (dove successivo viene definito in base all'ordine di sincronizzazione).
  • Un'azione che avvia la sincronizzazione di un thread, con la prima azione nel thread che avvia.
  • La scrittura del valore predefinito (zero, falso o null) per ogni variabile viene sincronizzata con la prima azione in ogni thread. Sebbene possa sembrare un po 'strano scrivere un valore predefinito su una variabile prima che l'oggetto che contiene la variabile sia allocato, concettualmente ogni oggetto viene creato all'inizio del programma con i suoi valori inizializzati di default.
  • L'azione finale in un thread T1 si sincronizza con qualsiasi azione in un altro thread T2 che rileva che T1 è terminato. T2 può ottenere ciò chiamando T1.isAlive() o T1.join().
  • Se il thread T1 interrompe il thread T2, l'interrupt di T1 si sincronizza, con qualsiasi punto in cui qualsiasi altro thread (incluso T2) determina che T2 è stato interrotto (avendo generato un InterruptedException o richiamando Thread.interrupted o Thread.isInterrupted) .

Si dice nulla che "la costruzione di un oggetto accade prima eventuali chiamate ai metodi sull'oggetto. Il accade-prima relazione tuttavia, afferma che c'è una accade-prima bordo dall'estremità di un costruttore di un oggetto all'inizio di un finalizzatore (§12.6) per quell'oggetto., che può essere un suggerimento circa che non c'è un accade-prima bordo dalla fine di un costruttore di un oggetto all'inizio di un metodo arbitrario!

+0

"[...] che tipo di implica che non esiste un fronte prima -vista dalla fine di un costruttore di un oggetto all'inizio di un metodo arbitrario!". Questa "implicazione" non è corretta. – Grodriguez

+0

Oh, certo, non è un'implicazione formale. Tuttavia, il fatto che affermino che la fine del costruttore si verifica prima dell'inizio del metodo di finalizzazione, dovremmo dire "suggerimenti su" che questo potrebbe non essere vero per i metodi arbitrari. – aioobe

+0

@aioobe no, questo è solo per casi come il seguente codice: 'new SomeObject()', cioè chiamando il costruttore ma non memorizzando il riferimento. Quindi l'oggetto può essere immediatamente raccolto e il finalizzatore può essere immediatamente chiamato. La frase a cui ti riferisci si limita a fare in modo che il costruttore sia ancora finito prima dell'esecuzione del finalizzatore. –

4

Non esiste alcuna relazione before-before tra la fine di un costruttore e le chiamate di metodo e, come tale, è possibile che un thread inizi a costruire l'istanza e rendi disponibile il riferimento e che un altro thread acquisisca tale riferimento e inizi a chiamare il saluto() metodo su un oggetto parzialmente costruito. La sincronizzazione in greet() in realtà non risolve questo problema.

Se pubblichi un'istanza tramite il celebre schema di blocco a doppia verifica, diventa più facile vedere come. Se esistesse una tale relazione prima, avrebbe dovuto essere al sicuro anche se si utilizza DCLP.

public class Foo { 
    private boolean needsGreeting = true; 

    public synchronized void greet() { 
     if (needsGreeting) { 
      System.out.println("Hello."); 
      needsGreeting = false; 
     } 
    } 
} 

class FooUser { 
    private static Foo foo; 

    public static Foo getFoo() { 
     if (foo == null) { 
      synchronized (FooUser.class) { 
       if (foo == null) { 
        foo = new Foo(); 
       } 
      } 
     } 
     return foo; 
    } 
} 

Se più thread chiamano FooUser.getFoo().greet() allo stesso tempo, un thread potrebbe costruire l'istanza di Foo, ma un altro thread potrebbe trovare prematuramente un riferimento Foo non nullo, e chiamare greet() e trovare needsGreeting è ancora falso.

Un esempio di questo è menzionato in Java Concurrency in Practice (3.5).

+0

Quindi questo è vero, anche se l'assegnazione a 'pippo' è terminata * dopo che *' new Foo() 'è stato completamente valutato? Hai un riferimento che conferma questo? – aioobe

+0

L'assegnazione a pippo viene eseguita dopo che il nuovo Foo() viene chiamato * solo * in quel thread in cui viene eseguito. Dalla prospettiva di altri thread, potrebbe non essere (o non sembrare). I compilatori e processori sono autorizzati a riordinare le esecuzioni e non a svuotare le modifiche fintanto che è coerente nel thread in cui viene eseguito il codice. Questo è ciò che la visibilità è tutto, dopo tutto. Ed è per questo che DCLP è rotto: visibilità. La sezione del modello di memoria Java delle specifiche del linguaggio sarebbe un buon riferimento. La corretta visibilità è fornita solo con una relazione appropriata prima-accade. – sjlee

+0

Anche questo potrebbe essere utile: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html – sjlee

Problemi correlati