Le relazioni avvenute prima con i campi volatili e i blocchi sincronizzati in Java e il loro impatto sulle variabili non volatili?

Sono ancora piuttosto nuovo al concetto di threading e cerco di capirne di più. Recentemente, mi sono imbattuto in un post sul blog What Volatile Means in Java di Jeremy Manson, in cui scrive:

Quando un thread scrive su una variabile volatile e un altro thread vede quella scrittura, il primo thread indica il secondo su tutti i contenuti della memoria fino a quando non ha eseguito la scrittura su quella variabile volatile. […] tutto il contenuto della memoria visto da Thread 1, prima che abbia scritto su [volatile] ready , deve essere visibile a Thread 2, dopo aver letto il valore true per ready . [enfasi aggiunta da me]

Questo significa che tutte le variabili (volatili o meno) contenute nella memoria del Thread 1 al momento della scrittura nella variabile volatile diventeranno visibili a Thread 2 dopo aver letto quella variabile volatile? Se è così, è ansible risolvere questa affermazione insieme dalla documentazione ufficiale di Java / fonti Oracle? E da quale versione di Java in poi funzionerà?

In particolare, se tutti i thread condividono le seguenti variabili di class:

 private String s = "running"; private volatile boolean b = false; 

E il thread 1 esegue quanto segue prima:

 s = "done"; b = true; 

E il thread 2 viene poi eseguito in seguito (dopo che il thread 1 ha scritto nel campo volatile):

 boolean flag = b; //read from volatile System.out.println(s); 

Questo sarebbe garantito per stampare “fatto”?

Cosa succederebbe se invece di dichiarare b come volatile inserissi la scrittura e la lettura in un blocco synchronized ?

Inoltre, in una discussione dal titolo ” Le variabili statiche sono condivise tra i thread? “, @TREE scrive :

Non utilizzare volatili per proteggere più di un pezzo di stato condiviso.

Perché? (Scusate, non posso ancora commentare altre domande, o avrei chiesto lì …)

Sì, è garantito che il thread 2 stamperà “done”. Ovviamente, questo è se la scrittura su b nella Discussione 1 in realtà accade prima della lettura da b nella Discussione 2, piuttosto che succedere allo stesso tempo, o prima!

Il cuore del ragionamento qui è la relazione “accade prima” . Le esecuzioni di programmi multithread sono viste come fatte di eventi. Gli eventi possono essere correlati dalle relazioni di happen-before, che dicono che un evento si verifica prima di un altro. Anche se due eventi non sono direttamente correlati, se è ansible tracciare una catena di relazioni accadute prima di un evento in un altro, allora si può dire che uno si verifica prima dell’altro.

Nel tuo caso, hai i seguenti eventi:

  • Thread 1 scrive su s
  • Il thread 1 scrive su b
  • Thread 2 legge da b
  • Thread 2 legge da s

E le seguenti regole entrano in gioco:

  • “Se x e y sono azioni dello stesso thread e x viene prima di y in ordine di programmazione, quindi hb (x, y).” (la regola dell’ordine del programma )
  • “Una scrittura su un campo volatile (§8.3.1.4) avviene prima di ogni successiva lettura di quel campo.” (la regola volatile )

Quindi accade il seguente: prima esistono relazioni:

  • Il thread 1 scrive su s prima che Thread 1 scriva su b (regola ordine programma)
  • Thread 1 scrive su b prima che Thread 2 legga b (regola volatile)
  • Le letture del thread 2 da b avvengono prima che il thread 2 legga s (regola ordine programma)

Se segui questa catena, puoi vedere come risultato:

  • Il thread 1 scrive su s prima che il thread 2 legga s

Cosa succederebbe se invece di dichiarare b come volatile, inserissi la scrittura e la lettura in un blocco sincronizzato?

Se e solo se proteggi tutti i blocchi sincronizzati con lo stesso blocco, avrai la stessa garanzia di visibilità del tuo esempio volatile . Avrai inoltre l’esclusione reciproca dall’esecuzione di tali blocchi sincronizzati.

Non utilizzare volatili per proteggere più di un pezzo di stato condiviso.

Perché?

volatile non garantisce atomicità: nel tuo esempio la variabile s potrebbe anche essere stata modificata da altri thread dopo la scrittura che stai visualizzando; il thread di lettura non avrà alcuna garanzia sul valore che vede. La stessa cosa vale per le scritture che si verificano dopo la lettura del volatile , ma prima della lettura di s .

Ciò che è sicuro di fare, e fatto in pratica, è la condivisione di uno stato immutabile accessibile dal riferimento scritto a una variabile volatile . Quindi forse questo è il significato inteso da “un pezzo di stato condiviso”.

è ansible risolvere questa affermazione insieme dalla documentazione ufficiale di Java / fonti Oracle?

Citazioni dalla specifica:

17.4.4. Ordine di sincronizzazione

Una scrittura su una variabile volatile v (§8.3.1.4) si sincronizza con tutte le letture successive di v da qualsiasi thread (dove “successivo” è definito in base all’ordine di sincronizzazione).

17.4.5. Happens-before Order

Se x e y sono azioni dello stesso thread e x viene prima di y in ordine di programmazione, quindi hb (x, y).

Se un’azione x si sincronizza con una seguente azione y, allora abbiamo anche hb (x, y).

Questo dovrebbe essere sufficiente.

E da quale versione di Java in poi funzionerà?

Java Language Specification, 3rd Edition ha introdotto la riscrittura delle specifiche del modello di memoria che è la chiave delle garanzie di cui sopra. NB la maggior parte delle versioni precedenti funzionava come se le garanzie esistessero e molte linee di codice dipendevano effettivamente da esso. Le persone erano sorprese quando hanno scoperto che le garanzie non erano state effettivamente lì.

Questo sarebbe garantito per stampare “fatto”?

Come detto in Java Concurrency in Practice :

Quando il thread A scrive su una variabile volatile e successivamente il thread B legge quella stessa variabile, i valori di tutte le variabili che erano visibili ad A prima della scrittura nella variabile volatile diventano visibili a B dopo aver letto la variabile volatile .

Quindi , questo garantisce di stampare “fatto”.

Cosa succederebbe se invece di dichiarare b come volatile, inserissi la scrittura e la lettura in un blocco sincronizzato?

Anche questo garantirà lo stesso.

Non utilizzare volatili per proteggere più di un pezzo di stato condiviso.

Perché?

Perché, volatile garantisce solo visibilità. Non garantisce l’atomicità. Se abbiamo due scritture volatili in un metodo a cui si accede da un thread A e un altro thread B sta accedendo a tali variabili volatili, quindi mentre il thread A sta eseguendo il metodo potrebbe essere ansible che il thread A venga preempito dal thread B nel metà delle operazioni (ad esempio dopo la prima scrittura volatile ma prima della seconda volatile scrivere dal thread A ). Quindi, garantire l’atomicità della synchronization operativa è la via più praticabile.