2009-06-13 2 views
11

domanda Forse simile:Perché le variabili in Java non sono volatili per impostazione predefinita?

Do you ever use the volatile keyword in Java?


Oggi stavo debug di mio gioco; Aveva un problema di thread molto difficile che si presentava ogni pochi minuti, ma era difficile da riprodurre. Quindi prima ho aggiunto la parola chiave synchronized a ciascuno dei miei metodi. Non ha funzionato. Quindi ho aggiunto la parola chiave volatile a ogni campo. Il problema sembrava risolversi da solo.

Dopo alcuni esperimenti, ho trovato che il campo responsabile era un oggetto GameState che teneva traccia dello stato attuale del mio gioco, che può essere in riproduzione o occupato. Se occupato, il gioco ignora l'input dell'utente. Quello che avevo era un thread che modificava costantemente la variabile state, mentre il thread Event legge la variabile state. Tuttavia, dopo che un thread ha modificato la variabile, sono necessari alcuni secondi affinché l'altro thread riconosca le modifiche, il che causa il problema.

È stato risolto rendendo la variabile di stato volatile.

Perché non sono variabili in Java volatile per impostazione predefinita e quale motivo non utilizzare la parola chiave volatile?

+11

Per lo stesso motivo, i metodi non sono sincronizzati per impostazione predefinita. –

+6

@mP: io non la penso così. Volatile e sincronizzato non sono equivalenti: la volatilità non può causare deadlock. –

risposta

32

Per farla breve, le variabili volatili, siano esse in Java o C#, non vengono mai memorizzate nella cache localmente all'interno del thread. Questo non ha molte implicazioni a meno che non si abbia a che fare con una CPU multiprocessore/multicore con thread che eseguono su core diversi, poiché guarderebbero la stessa cache. Quando si dichiara una variabile come volatile, tutte le letture e le scritture vengono direttamente e passano direttamente alla posizione di memoria principale effettiva; non ci sono cache coinvolte. Questo ha implicazioni quando si tratta di ottimizzazione, e farlo inutilmente (quando la maggior parte delle variabili non ha bisogno di essere volatili) infliggerebbe una penalizzazione delle prestazioni (insignificante come potrebbe o non potrebbe essere) per un guadagno relativamente piccolo.

+6

Anche con un processore single core, il compilatore (incluso il compilatore JIT) può decidere di ottimizzare l'accesso alla variabile. –

+0

@Chris: provengo da uno sfondo .NET, quindi Java potrebbe essere diverso, ma non penserei che una variabile di istanza possa essere ottimizzata a meno che non abbia rilevato che non è mai stata utilizzata e le variabili locali non possono essere dichiarate volatili (non ne avrebbe davvero bisogno), giusto? –

+8

@Adam: non può ottimizzarlo completamente. Tuttavia, qualcosa come "y = x * x" può solo andare in memoria per ottenere il valore di x una volta, invece di due. Questo è un esempio banale ... ora immagina che x sia distribuito su un ciclo e controllato più volte. Se il compilatore non ti vede mai cambiare il valore di x, potrebbe decidere di leggerlo una volta e tenerlo in giro localmente. –

5

Poiché il compilatore non è in grado di ottimizzare le variabili volatili.

volatile indica al compilatore che la variabile può cambiare in qualsiasi momento. Pertanto, non può presumere che la variabile non cambierà e ottimizzerà di conseguenza.

2

La dichiarazione delle variabili volatili ha generalmente un impatto enorme sulle prestazioni. Sui tradizionali sistemi a thread singolo, era relativamente facile sapere cosa doveva essere volatile; erano quelle cose che accedevano all'hardware.

Su multithreading può essere un po 'più complesso, ma in genere incoraggerei l'utilizzo di notifiche e code di eventi per gestire i dati di passaggio tra i thunders in leau delle variabili magiche. In Java potrebbe non avere molta importanza; in C/C++ si finirebbe nei guai quando quelle variabili non possono essere impostate atomicamente dall'hardware sottostante.

+0

Sono curioso, la mia unica esperienza con variabili volatili è specificamente per l'accesso multithread. Quale sarebbe il ragionamento dietro le variabili volatili in un ambiente a thread singolo? –

+1

@Adam: in Java e C#, non ne sono così sicuro, anche se penso che JNI potrebbe farti giocare alcuni giochi. In un ambiente C/C++, volatile è stato fondamentale per indicare che questa memoria può essere cambiata da qualche altra parte oltre al programma in esecuzione, cioè, l'hardware che è mappato a quella posizione di memoria. Poiché Java e C# generalmente non sono così vicini all'hardware, potrebbe essere un gioco diverso. –

+0

@AdamRobinson: anche in un singolo ambiente con thread, Java si riserva il diritto di riordinare i comandi indipendenti. Quindi, ad esempio: 'x = 1; y = 2; 'può essere cambiato in' y = 2; x = 1; '. Ma 'x = z ++; y = z ++; 'non dovrebbe essere modificato in' y = z ++; x = z ++; '. La JVM non dovrebbe riordinare i comandi che hanno visibilità esterna. Ad esempio 'x = 1; System.out.println ("x è impostato"); y = 2; 'non può essere riordinato. Se non ti fidi della logica di visibilità esterna di Javas (diciamo se stai monitorando la memoria reale), puoi usare volatile per contrassegnare la variabile come qualcosa che non dovrebbe essere ottimizzato. –

6

Mentre altri hanno ragione nel sottolineare perché sarebbe una cattiva idea impostarsi su volatile, c'è un altro punto da fare: c'è molto probabilmente un bug nel codice. Le variabili raramente devono essere rese volatili: esiste sempre un modo per sincronizzare correttamente l'accesso alle variabili (tramite parola chiave sincronizzata o usando oggetti AtomicXxx da java.util.concurrency): le eccezioni includono il codice JNI che manipola questi (che non è vincolato da direttive di sincronizzazione).

Quindi, invece di aggiungere volatile, è possibile capire perché ha risolto il problema. Non è l'unico modo per risolverlo, e probabilmente c'è un modo migliore.

+1

Anche se questo può o non può essere vero, è del tutto possibile che stia cercando di scrivere codice senza blocco. Non è sempre necessario ottenere un blocco esclusivo o utilizzare un semaforo se tutto quello che stai facendo è leggere. Solo il mio .02 –

+0

Vero, è possibile, e i volatili sono a senso unico. Ma il sovraccarico di volatile (dovuto a motivi che altri segnalano) può superare quello della sincronizzazione esplicita e difficilmente inferiore. Soprattutto se si considerano le ottimizzazioni ottimizzate di j.u.concurrent o anche la semplice sincronizzazione. – StaxMan

10

volatili siano realmente necessari solo quando si sta cercando di scrivere di basso livello, codice libero-lock thread-safe. La maggior parte del codice, probabilmente non dovrebbe essere sia thread-safe o libera blocco. Nella mia esperienza, la programmazione lock-free vale la pena di tentare solo dopo aver scoperto che la versione più semplice che compie con il blocco si sta verificando un notevole calo di prestazioni a causa del blocco.

L'alternativa più piacevole è quella di utilizzare altri blocchi predefiniti in java.util.concurrent, alcuni dei quali sono senza blocco, ma non si scherza con la testa tanto quanto cercare di fare tutto da soli a un livello basso.

La volatilità ha i suoi costi di prestazione, e non c'è motivo per cui il codice più dovrebbe sostenere tali costi.

8

Personalmente penso che i campi avrebbero dovuto essere finale per impostazione predefinita e mutabile solo con una parola chiave in più, ma che barca ha navigato lungo tempo fa. ;)