2012-01-07 9 views
19

Questo riguarda il volatile sulle spalle. Scopo: voglio raggiungere una visibilità leggera dei vars. La coerenza di a_b_c non è importante. Ho un sacco di soldi e non voglio renderli tutti volatili.Piggyback volatile. È abbastanza per la visibilità?

È questo il codice threadsafe?

class A { 
    public int a, b, c; 
    volatile int sync; 

    public void setup() { 
     a = 2; 
     b = 3; 
     c = 4; 
    } 

    public void sync() { 
     sync++; 
    } 
} 

final static A aaa = new A(); 

Thread0: 
aaa.setup(); 
end 

Thread1: 
for(;;) {aaa.sync(); logic with aaa.a, aaa.b, aaa.c} 

Thread2: 
for(;;) {aaa.sync(); logic with aaa.a, aaa.b, aaa.c} 
+0

Se la coerenza non è importante, perché ti importa se le variabili si sono sincronizzate? – cHao

+0

Non mi interessa la coerenza ma mi interessa la visibilità. – temper

+0

Quindi rendili pubblici? Ma allora perché taggare la domanda con la sincronizzazione? –

risposta

26

Java Memory Model definisce il accade-prima relazione che ha le seguenti proprietà (tra gli altri):

  • "ogni azione in un thread accade-prima di ogni azione in quel filo che viene dopo nell'ordine programma" (programma regola ordine)
  • "Una scrittura un campo volatili accade-prima di ogni lettura successiva dello stesso volatile" (regola variabile volatili)

Queste due proprietà insieme con transitività della accade-prima rapporto implica garanzie di visibilità che OP cerca nel seguente modo:

  1. una scrittura a in filo 1 accade-prima una scrittura sync in una chiamata a sync() in filo 1 (regola ordine programma).
  2. La scrittura alla sync nella chiamata a sync() in filo 1 accade-prima una lettura ad sync in una chiamata a sync in filo 2 (regola variabile volatile).
  3. La lettura dalla sync nella chiamata a sync() in filo 2 accade-prima una lettura da a in filo 2 (programma regola ordine).

Ciò implica che la risposta alla domanda è sì, cioè la chiamata a sync() in ogni iterazione in fili 1 e 2 assicura la visibilità di modifiche a, b e c all'altro filo (s). Si noti che questo garantisce solo la visibilità. Non esistono garanzie di mutua esclusione e quindi tutti gli invarianti vincolanti a, b e c possono essere violati.

Vedere anche Java theory and practice: Fixing the Java Memory Model, Part 2. In particolare la sezione "Nuove garanzie per volatili" che dice

Secondo il nuovo modello di memoria, quando il filo A scrive un volatili variabile V, e filo B legge da V, i valori delle variabili che erano visibile A al momento in cui V è stato scritto sono garantiti ora visibile a B.

+2

@PhilippWendler Le due regole implicano la visibilità delle modifiche a 'a',' b' e 'c' fatte nel thread 1 al thread 2 nel modo seguente: cambia in' a' in thread 1 _happens-before_ a write in 'sync 'in una successiva chiamata a' sync() 'nella discussione 1 (regola 1) che _happens-before_ una lettura da' sync' in una chiamata a 'sync' nella discussione 2 (regola 2) che _happens-before_ una lettura da' a' nella discussione 2 (di nuovo, regola 1). –

+1

@PhilippWendler Vedi anche l'articolo a cui mi sono collegato, in particolare la sezione "Nuove garanzie per volatili". Una citazione più pertinente: "Sotto il nuovo modello di memoria, quando il thread A scrive su una variabile volatile V, e il thread B legge su V, tutti i valori delle variabili che erano visibili ad A al momento in cui V è stato scritto sono garantiti ora visibili a B. " –

+0

Puoi spiegare' cambia in a in un thread 1 accade - prima di scrivere per sincronizzare in una successiva chiamata a sync() nella discussione 1 (regola 1) '. Quello che vedo è '{aaa.sync(); logica con aaa.a, aaa.b, aaa.c} 'così' sync() 'viene chiamato prima dell'accesso a' a'. Grazie –

0

Non hanno veramente a sincronizzare manualmente a tutti, basta usare una struttura dati sincronizzati automaticamente, come java.util.concurrent.atomic.AtomicInteger.

In alternativa, è possibile effettuare il metodosynchronized.

+1

Non mi interessa affatto una variabile di sincronizzazione – temper

3

L'incremento di un valore tra i thread non è mai thread-safe con solo volatile. Questo assicura che ogni thread abbia un valore aggiornato, non che l'incremento sia atomico, perché a livello di assemblatore il tuo ++ è in realtà più istruzioni che possono essere intercalate.

È necessario utilizzare AtomicInteger per un rapido incremento atomico.

Modifica: Leggere di nuovo ciò di cui si ha bisogno è in realtà una barriera di memoria. Java non ha istruzioni di fence di memoria, ma è possibile utilizzare un blocco per l'effetto collaterale della fence di memoria. In tal caso dichiarare il metodo di sincronizzazione sincronizzato per introdurre un recinto implicito:

void synchronized sync() { 
    sync++; 
} 
+1

synC++ è solo un trucco per leggere e scrivere o una memoria volatile .. – temper

+0

@temper: L'ho capito. La modifica è la risposta alla tua domanda. – Tudor

1

Da javadoc:

Uno sblocco (blocco sincronizzato o metodo uscita) di un monitor accade-prima di ogni successivo blocco (blocco sincronizzato o metodo voce) di quello stesso monitor. E poiché la relazione before-before è transitiva, tutte le azioni di una discussione prima dello sblocco avvengono prima di tutte le azioni successive a qualsiasi thread che blocca il monitoraggio .

Una scrittura su un campo volatile avviene prima di ogni successiva lettura di nello stesso campo. Le scritture e le letture dei campi volatili hanno effetti di consistenza della memoria simili a come entrate e uscite dai monitor, ma lo non comporta il blocco dell'esclusione reciproca.

quindi penso che la scrittura var volatile non è un equivalente di sincronizzazione in questo caso e che non garantisce accade-prima che l'ordine e la visibilità delle variazioni del Thread1 a Thread2

1

Il motivo è solitamente simile a questo.

public void setup() { 
    a = 2; 
    b = 3; 
    c = 4; 
    sync(); 
} 

Tuttavia, mentre questo garantisce che gli altri thread vedano questa modifica, gli altri thread possono vedere una modifica incompleta. per esempio. il Thread2 potrebbe vedere a = 2, b = 3, c = 0. o anche eventualmente a = 2, b = 0, c = 4;

L'utilizzo della sincronizzazione() sul lettore non aiuta molto.

+0

Ho incomprensioni. in thread1 invoco il metodo di installazione. dopo di esso in thread2 posso vedere b = 0? – gstackoverflow

+0

@gstackoverflow Sono necessarie due parti. Una barriera di scrittura all'estremità sul lato di scrittura e una barriera di lettura all'inizio sul lato di lettura. Questo ti assicura di leggere ciò che è stato scritto in un altro thread. Scrivere su un volatile implica una barriera di scrittura, la lettura da un volatile implica una barriera di lettura. –

+1

@PeterLawrey, ma nella domanda ha * avuto * una barriera di lettura, giusto? Il 'aaa.sync()' in Thread 1 e Thread 2 è considerato sufficiente? – Pacerier

Problemi correlati