Le variabili statiche sono condivise tra i thread?

Il mio insegnante in una class java di livello superiore sul threading ha detto qualcosa di cui non ero sicuro.

Ha dichiarato che il seguente codice non aggiornerebbe necessariamente la variabile ready . Secondo lui, i due thread non condividono necessariamente la variabile statica, in particolare nel caso in cui ogni thread (thread principale contro ReaderThread) è in esecuzione sul proprio processore e quindi non condivide gli stessi registri / cache / etc e uno La CPU non aggiornerà l’altra.

Essenzialmente, ha detto che è ansible che ready sia aggiornato nel thread principale, ma NON nel ReaderThread, in modo che ReaderThread giri all’infinito. Ha anche affermato che era ansible che il programma stampasse “0” o “42”. Capisco come ’42’ potrebbe essere stampato, ma non ‘0’. Ha menzionato questo sarebbe il caso quando la variabile number è impostata sul valore predefinito.

Ho pensato che forse non è garantito che la variabile statica venga aggiornata tra i thread, ma questo mi sembra molto strano per Java. Rendere ready volatile correggere questo problema?

Ha mostrato questo codice:

  NoVisibility di class pubblica {  
     pronto statico privato booleano;  
     numero int statico privato;  
     ReaderThread di class statica privata estende Thread {   
         public void run () {  
             while (! ready) Thread.yield ();  
             System.out.println (numero);  
         }  
     }  
     public static void main (String [] args) {  
         nuovo ReaderThread (). start ();  
         numero = 42;  
         pronto = vero;  
     }  
 } 

Non c’è alcun problema di visibilità specifico per le variabili statiche. C’è un problema di visibilità imposto dal modello di memoria della JVM. Ecco un articolo che parla del modello di memoria e di come le scritture diventano visibili ai thread . Non puoi contare sulle modifiche che un thread rende visibili agli altri thread in modo tempestivo (in realtà la JVM non ha alcun obbligo di rendere quelle modifiche visibili a tutti), a meno che tu non stabilisca una relazione di happen-before , ecco una citazione da quel collegamento (fornito nel commento di Jed Wesley-Smith):

Il capitolo 17 della specifica del linguaggio Java definisce la relazione prima-succede su operazioni di memoria come letture e scritture di variabili condivise. I risultati di una scrittura da un thread sono garantiti per essere visibili a una lettura da un altro thread solo se avviene l’operazione di scrittura, prima dell’operazione di lettura. I costrutti sincronizzati e volatili, così come i metodi Thread.start () e Thread.join (), possono formare relazioni prima-accade. In particolare:

  • Ogni azione in un thread avviene prima di ogni azione in quel thread che viene in seguito nell’ordine del programma.

  • Uno sblocco (blocco sincronizzato o uscita metodo) di un monitor avviene prima di ogni blocco successivo (blocco sincronizzato o entrata metodo) dello stesso monitor. E poiché la relazione happen-before è transitiva, tutte le azioni di un thread prima dello sblocco avvengono, prima di tutte le azioni successive a qualsiasi thread che blocca quel monitoraggio.

  • Una scrittura in un campo volatile avviene prima di ogni successiva lettura di quello stesso campo. Scritture e letture di campi volatili hanno effetti di consistenza della memoria simili all’ingresso e all’uscita dai monitor, ma non comportano il blocco dell’esclusione reciproca.

  • Una chiamata per iniziare su un thread avviene prima di qualsiasi azione nel thread avviato.

  • Tutte le azioni in un thread avvengono, prima che qualsiasi altro thread ritorni correttamente da un join su quel thread.

Stava parlando di visibilità e non doveva essere preso alla lettera.

Le variabili statiche sono effettivamente condivise tra i thread, ma le modifiche apportate in un thread potrebbero non essere visibili immediatamente a un altro thread, facendo sembrare che ci siano due copie della variabile.

Questo articolo presenta una visione coerente con il modo in cui ha presentato le informazioni:

Innanzitutto, devi capire qualcosa del modello di memoria Java. Ho faticato un po ‘nel corso degli anni per spiegarlo brevemente e bene. Ad oggi, il modo migliore che riesco a pensare per descriverlo è se lo immagini in questo modo:

  • Ogni thread in Java si svolge in uno spazio di memoria separato (questo è chiaramente falso, quindi portami con me su questo).

  • È necessario utilizzare meccanismi speciali per garantire che la comunicazione avvenga tra questi thread, come faresti su un sistema di trasmissione di messaggi.

  • Le scritture di memoria che avvengono in un thread possono “filtrare” e essere viste da un altro thread, ma questo non è affatto garantito. Senza una comunicazione esplicita, non è ansible garantire quali scritture vengono viste da altri thread o persino l’ordine in cui vengono visualizzati.

modello di filo

Ma ancora una volta, questo è semplicemente un modello mentale per pensare al threading e al volatile, non alla lettera come funziona la JVM.

Fondamentalmente è vero, ma in realtà il problema è più complesso. La visibilità dei dati condivisi può essere influenzata non solo dalle cache della CPU, ma anche dall’esecuzione di istruzioni fuori uso.

Pertanto Java definisce un modello di memoria , che stabilisce in quali circostanze i thread possono vedere lo stato coerente dei dati condivisi.

Nel tuo caso particolare, aggiungendo visibilità alle garanzie volatile .

Sono “condivisi”, ovviamente, nel senso che entrambi si riferiscono alla stessa variabile, ma non necessariamente vedono gli aggiornamenti degli altri. Questo è vero per qualsiasi variabile, non solo statica.

E in teoria, le scritture fatte da un altro thread possono apparire in un ordine diverso, a meno che le variabili non siano dichiarate volatile o le scritture siano esplicitamente sincronizzate.

All’interno di un singolo programma di caricamento classi, i campi statici sono sempre condivisi. Per ThreadLocal esplicitamente i dati ai thread, ti consigliamo di utilizzare una funzione come ThreadLocal .

Quando si inizializza la variabile di tipo primitivo statico, java default assegna un valore per le variabili statiche

 public static int i ; 

quando definisci la variabile in questo modo, il valore predefinito di i = 0; ecco perché c’è la possibilità di ottenerti 0. quindi il thread principale aggiorna il valore di boolean pronto per true. poiché ready è una variabile statica, il thread principale e l’altro riferimento allo stesso indirizzo di memoria, quindi la variabile ready cambia. quindi il filo secondario esce dal ciclo while e stampa il valore. quando si stampa il valore, il valore inizializzato del numero è 0. se il processo del thread ha passato il ciclo prima della variabile numero di aggiornamento del thread principale. allora c’è la possibilità di stampare 0

@dontocsata puoi tornare dal tuo insegnante e fargli una piccola lezione 🙂

poche note dal mondo reale e indipendentemente da ciò che vedi o che ti viene detto. NOTA BENE, le parole qui sotto riguardano questo caso particolare nell’ordine esatto mostrato.

La seguente variabile 2 risiederà sulla stessa linea di cache sotto praticamente qualsiasi architettura conosciuta.

 private static boolean ready; private static int number; 

Thread.exit (thread principale) è garantito per uscire e exit è garantito per causare una recinzione di memoria, a causa della rimozione del thread del gruppo di thread (e molti altri problemi). (è una chiamata sincronizzata e non vedo alcun modo di essere implementato senza la parte di sincronizzazione poiché il ThreadGroup deve terminare anche se non vengono lasciati thread di daemon, ecc.).

Il thread avviato ReaderThread manterrà il processo ReaderThread poiché non è un demone! Così ready e il number verrà risciacquato insieme (o il numero precedente se si verifica un cambio di contesto) e non c’è un vero motivo per riordinare in questo caso almeno non riesco nemmeno a pensarne uno. Avrai bisogno di qualcosa di veramente strano per vedere altro che 42 . Ancora una volta presumo che entrambe le variabili statiche si trovino nella stessa riga della cache. Non riesco a immaginare una linea cache lunga 4 byte O una JVM che non li assegnerà in un’area continua (linea cache).