Au travail aujourd'hui, je suis tombé sur le mot clé volatile
en Java. Ne le connaissant pas très bien, j'ai trouvé cette explication:
Étant donné le détail dans lequel cet article explique le mot clé en question, l'utilisez-vous déjà ou pouvez-vous jamais voir un cas dans lequel vous pourriez utiliser ce mot clé de manière correcte?
volatile
a une sémantique pour la visibilité de la mémoire. Fondamentalement, la valeur d'un champ volatile
devient visible pour tous les lecteurs (les autres threads en particulier) à la fin d'une opération d'écriture. Sans volatile
, les lecteurs pourraient voir une valeur non mise à jour.
Pour répondre à votre question: Oui, j’utilise une variable volatile
pour contrôler si du code continue une boucle. La boucle teste la valeur volatile
et continue si elle est true
. La condition peut être définie sur false
en appelant une méthode "stop". La boucle voit false
et se termine lorsqu'elle teste la valeur après l'exécution de la méthode d'arrêt.
Le livre " Java Concurrency in Practice ", que je recommande vivement, donne une bonne explication de volatile
. Ce livre est écrit par la même personne que celle qui a écrit l'article d'IBM auquel il est fait référence dans la question (en fait, il cite son livre au bas de cet article). Mon utilisation de volatile
est ce que son article appelle "l'indicateur d'état de modèle 1".
Si vous voulez en savoir plus sur le fonctionnement de volatile
sous le capot, consultez la page le modèle de mémoire Java . Si vous voulez aller au-delà de ce niveau, consultez un bon livre d'architecture d'ordinateur tel que Hennessy & Patterson et consultez la cohérence du cache et la cohérence du cache.
“… Le modificateur volatile garantit que tout thread qui lit un champ verra la dernière valeur écrite.”- Josh Bloch
Si vous envisagez d’utiliser volatile
, lisez le paquet Java.util.concurrent
qui traite du comportement atomique.
Le message de Wikipédia sur Singleton Pattern indique l’utilisation volatile.
Point important à propos de volatile
:
synchronized
et volatile
et les verrous.synchronized
. L'utilisation du mot clé synchronized
avec une variable est illégale et entraînera une erreur de compilation. Au lieu d’utiliser la variable synchronized
en Java, vous pouvez utiliser la variable Java volatile
, qui ordonnera aux threads JVM de lire la valeur de la variable volatile
dans la mémoire principale et de ne pas la mettre en cache localement.volatile
.Exemple d'utilisation de volatile
:
public class Singleton {
private static volatile Singleton _instance; // volatile variable
public static Singleton getInstance() {
if (_instance == null) {
synchronized (Singleton.class) {
if (_instance == null)
_instance = new Singleton();
}
}
return _instance;
}
}
Nous créons instance paresseusement au moment de la première demande.
Si nous ne créons pas la variable _instance
volatile
, le thread qui crée l'instance de Singleton
n'est pas en mesure de communiquer avec l'autre thread. Ainsi, si le thread A crée l'instance Singleton et juste après la création, le processeur corrompt, etc., tous les autres threads ne pourront pas voir la valeur de _instance
comme non nulle et ils croiront qu'elle est toujours affectée à la valeur null.
Pourquoi cela arrive-t-il? Etant donné que les threads de lecteur ne font pas de verrouillage et tant que le thread d'écriture ne sort pas d'un bloc synchronisé, la mémoire ne sera pas synchronisée et la valeur de _instance
ne sera pas mise à jour dans la mémoire principale. Avec le mot clé Volatile en Java, cela est géré par Java lui-même et ces mises à jour seront visibles par tous les fils du lecteur.
Conclusion: Le mot clé
volatile
est également utilisé pour communiquer le contenu de la mémoire entre les threads.
Exemple d'utilisation de sans volatile:
public class Singleton{
private static Singleton _instance; //without volatile variable
public static Singleton getInstance(){
if(_instance == null){
synchronized(Singleton.class){
if(_instance == null) _instance = new Singleton();
}
}
return _instance;
}
Le code ci-dessus n'est pas thread-safe. Bien qu'il vérifie à nouveau la valeur de l'instance dans le bloc synchronisé (pour des raisons de performances), le compilateur JIT peut réorganiser le bytecode de manière à ce que la référence à l'instance soit définie avant que le constructeur n'ait terminé son exécution. Cela signifie que la méthode getInstance () renvoie un objet qui n'a peut-être pas été initialisé complètement. Pour rendre le code thread-safe, le mot-clé volatile peut être utilisé depuis Java 5 pour la variable d'instance. Les variables marquées comme étant volatiles ne sont visibles par les autres threads que lorsque le constructeur de l'objet a complètement terminé son exécution.
La source
volatile
usage in Java:
Les itérateurs de type fast-fast sont généralement mis en oeuvre à l'aide d'un compteur volatile
sur l'objet liste.
Iterator
, la valeur actuelle du compteur est incorporée dans l'objet Iterator
.Iterator
est effectuée, la méthode compare les deux valeurs de compteur et génère une ConcurrentModificationException
si elles sont différentes.L'implémentation d'itérateurs à sécurité intrinsèque est généralement légère. Ils s'appuient généralement sur les propriétés des structures de données de l'implémentation de liste spécifique. Il n'y a pas de schéma général.
volatile est très utile pour arrêter les threads.
Non pas que vous deviez écrire vos propres threads, Java 1.6 a beaucoup de pools de threads Nice. Mais si vous êtes sûr d'avoir besoin d'un fil, vous devez savoir comment l'arrêter.
Le motif que j'utilise pour les discussions est:
public class Foo extends Thread {
private volatile boolean close = false;
public void run() {
while(!close) {
// do work
}
}
public void close() {
close = true;
// interrupt here if needed
}
}
Remarquez comme il n'y a pas besoin de synchronisation
Un exemple courant d'utilisation de volatile
consiste à utiliser une variable volatile boolean
en tant qu'indicateur pour terminer un thread. Si vous avez démarré un thread et que vous souhaitez pouvoir l'interrompre en toute sécurité depuis un autre thread, vous pouvez le faire vérifier régulièrement un indicateur. Pour l'arrêter, définissez l'indicateur sur true. En rendant l'indicateur volatile
, vous pouvez vous assurer que le thread qui le vérifie verra qu'il a été défini lors de sa prochaine vérification sans avoir à utiliser un bloc synchronized
.
Personne n'a mentionné le traitement des opérations de lecture et d'écriture pour les types long et double variable. Les lectures et les écritures sont des opérations atomiques pour les variables de référence et pour la plupart des variables primitives, à l'exception des types de variables longues et doubles, qui doivent utiliser le mot clé volatile pour être des opérations atomiques. @lien
Oui, volatile doit être utilisé chaque fois que vous souhaitez qu'une variable mutable soit accessible par plusieurs threads. Ce n'est pas très courant car généralement, vous devez effectuer plus d'une opération atomique unique (par exemple, vérifier l'état de la variable avant de la modifier), auquel cas vous utiliseriez plutôt un bloc synchronisé.
Une variable déclarée avec le mot clé volatile
a deux qualités principales qui la rendent spéciale.
Si nous avons une variable volatile, elle ne peut pas être mise en cache dans la mémoire cache de l'ordinateur (microprocesseur) par aucun thread. L'accès a toujours eu lieu à partir de la mémoire principale.
S'il y a une opérationwritesur une variable volatile et que soudain une opérationreadest demandée, il est garanti que l'opérationwrite sera terminée avant l'opération de lecture.
Deux qualités ci-dessus déduisent que
Et d'autre part
volatile
est un moyen idéal de conserver une variable partagée comportant le nombre'n' de threads lus et un seul thread d'écriturepour y accéder. Une fois que nous avons ajouté le mot clé volatile
, c'est terminé. Pas d'autres frais généraux sur la sécurité des threads.À l'inverse,
Nousne pouvons pasutiliser uniquement le mot clé volatile
pour satisfaire une variable partagée qui dispose dede plusieurs threads d'écriture y accédant.
OMI deux scénarios importants autres que l’arrêt du fil dans lequel le mot clé volatile est utilisé sont
singleton object needs to be declared volatile
.boolean flag is declared as volatile
.volatile
garantit uniquement que tous les threads, même eux-mêmes, sont incrémentés. Par exemple: un compteur voit le même visage de la variable en même temps. Il n'est pas utilisé à la place de choses synchronisées, atomiques ou autres, il rend complètement les lectures synchronisées. Veuillez ne pas le comparer avec d'autres mots-clés Java. Comme le montre l'exemple ci-dessous, les opérations de variables volatiles sont également atomiques: elles échouent ou réussissent d'un coup.
package io.netty.example.telnet;
import Java.util.ArrayList;
import Java.util.List;
public class Main {
public static volatile int a = 0;
public static void main(String args[]) throws InterruptedException{
List<Thread> list = new ArrayList<Thread>();
for(int i = 0 ; i<11 ;i++){
list.add(new Pojo());
}
for (Thread thread : list) {
thread.start();
}
Thread.sleep(20000);
System.out.println(a);
}
}
class Pojo extends Thread{
int a = 10001;
public void run() {
while(a-->0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Main.a++;
System.out.println("a = "+Main.a);
}
}
}
Même si vous mettez volatile ou non, les résultats seront toujours différents. Mais si vous utilisez AtomicInteger comme ci-dessous, les résultats seront toujours les mêmes. C'est pareil avec synchronisé aussi.
package io.netty.example.telnet;
import Java.util.ArrayList;
import Java.util.List;
import Java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static volatile AtomicInteger a = new AtomicInteger(0);
public static void main(String args[]) throws InterruptedException{
List<Thread> list = new ArrayList<Thread>();
for(int i = 0 ; i<11 ;i++){
list.add(new Pojo());
}
for (Thread thread : list) {
thread.start();
}
Thread.sleep(20000);
System.out.println(a.get());
}
}
class Pojo extends Thread{
int a = 10001;
public void run() {
while(a-->0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Main.a.incrementAndGet();
System.out.println("a = "+Main.a);
}
}
}
Vous devrez utiliser un mot clé "volatile" ou "synchronisé" et tous les autres outils et techniques de contrôle de la concurrence que vous pourriez avoir à votre disposition si vous développez une application multithread. Un exemple d'une telle application est les applications de bureau.
Si vous développez une application qui serait déployée sur un serveur d'applications (Tomcat, JBoss AS, Glassfish, etc.), vous n'avez pas à gérer vous-même le contrôle de la concurrence, car il est déjà traité par le serveur d'applications. En fait, si je me souvenais bien, la norme Java EE interdisait tout contrôle de la simultanéité dans les servlets et les EJB, car il fait partie de la couche «infrastructure» dont vous êtes censé être libéré. Vous ne contrôlez la concurrence que dans une telle application si vous implémentez des objets singleton. Cela a même déjà été résolu si vous tricotez vos composants avec frameworkd, comme Spring.
Ainsi, dans la plupart des cas de développement Java où l'application est une application Web et utilisant un framework IoC tel que Spring ou EJB, vous n'avez pas besoin d'utiliser 'volatile'.
Oui, je l'utilise beaucoup - cela peut être très utile pour du code multi-thread. L'article que vous avez cité est bon. Bien qu'il y ait deux choses importantes à garder à l'esprit:
Absolument oui. (Et pas seulement en Java, mais aussi en C #.) Il est parfois nécessaire d’obtenir ou de définir une valeur qui est garantie comme une opération atomique sur votre plate-forme donnée, un int ou un booléen, par exemple, les frais généraux de blocage de fil. Le mot clé volatile vous permet de vous assurer que lorsque vous lisez la valeur, vous obtenez la valeur current et non une valeur en cache qui vient d'être rendue obsolète par une écriture sur un autre thread.
Chaque thread accédant à un champ volatile lira sa valeur actuelle avant de continuer, au lieu d'utiliser (potentiellement) une valeur en cache.
Seule la variable membre peut être volatile ou transitoire.
Il existe deux utilisations différentes du mot clé volatile.
Empêche JVM de lire les valeurs dans le registre et force son valeur à lire de la mémoire.
Un indicateur busy est utilisé pour empêcher un thread de continuer pendant que le périphérique est occupé et si l'indicateur n'est pas protégé par un verrou:
while (busy) {
/* do something else */
}
Le thread de test continuera lorsqu'un autre thread désactive l'indicateur busy:
busy = 0;
Cependant, étant donné que Occupé est fréquemment utilisé dans le processus de test, la machine virtuelle Java peut optimiser le test en plaçant la valeur Occupé dans un registre, puis tester le contenu du registre sans lire la valeur Occupé en mémoire avant chaque test. Le thread de test ne verra jamais occupé occupé et l'autre thread ne changera que la valeur de busy en mémoire, ce qui entraînera un blocage. Déclarer l'indicateur busy comme volatile force la lecture de sa valeur avant chaque test.
Réduit le risque d'erreur de cohérence de la mémoire.
L'utilisation de variables volatiles réduit le risque de erreurs de cohérence de la mémoire, car toute écriture dans une variable volatile établit une relation "se produit-avant" avec les lectures suivantes de cette même variable. Cela signifie que les modifications apportées à une variable volatile sont toujours visibles par les autres threads.
La technique de lecture, écriture sans erreurs de cohérence de mémoire est appelée action atomique.
Une action atomique est une action qui se produit effectivement en même temps. Une action atomique ne peut pas s'arrêter au milieu: elle se produit complètement ou ne se produit pas du tout. Aucun effet secondaire d'une action atomique n'est visible jusqu'à ce que l'action soit terminée.
Vous pouvez spécifier ci-dessous des actions atomiques:
À votre santé!
Volatile suit.
1> La lecture et l'écriture de variables volatiles par différents threads proviennent toujours de la mémoire, et non du propre cache ou du registre du processeur du thread. Ainsi, chaque thread traite toujours avec la dernière valeur . 2> Lorsque 2 threads différents fonctionnent avec la même instance ou des variables statiques dans le segment de mémoire, les actions des autres peuvent être considérées comme étant dans le désordre. Voir le blog de jeremy manson à ce sujet. Mais volatile aide ici.
La suite du code en cours d'exécution montre comment un certain nombre de threads peuvent s'exécuter dans un ordre prédéfini et imprimer des sorties sans utiliser de mot clé synchronisé.
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
Pour ce faire, nous pouvons utiliser le code courant complet suivant.
public class Solution {
static volatile int counter = 0;
static int print = 0;
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread[] ths = new Thread[4];
for (int i = 0; i < ths.length; i++) {
ths[i] = new Thread(new MyRunnable(i, ths.length));
ths[i].start();
}
}
static class MyRunnable implements Runnable {
final int thID;
final int total;
public MyRunnable(int id, int total) {
thID = id;
this.total = total;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
if (thID == counter) {
System.out.println("thread " + thID + " prints " + print);
print++;
if (print == total)
print = 0;
counter++;
if (counter == total)
counter = 0;
} else {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
// log it
}
}
}
}
}
}
Le lien github suivant a un fichier readme, qui donne une explication appropriée . https://github.com/sankar4git/volatile_thread_ordering
Les variables volatiles sont une synchronisation légère. Lorsque la visibilité des dernières données parmi tous les threads est requise et que l'atomicité peut être compromise, dans de telles situations, les variables volatiles doivent être préférées. Les variables de lecture sur volatiles renvoient toujours les écritures les plus récentes effectuées par un thread, car elles ne sont ni mises en cache dans des registres ni dans des caches où les autres processeurs ne peuvent pas voir. Volatile est Lock-Free. J'utilise volatile, lorsque le scénario répond aux critères mentionnés ci-dessus.
De la documentation Oracle page , la nécessité d’une variable volatile s’impose pour résoudre les problèmes de cohérence de la mémoire:
L'utilisation de variables volatiles réduit le risque d'erreurs de cohérence de la mémoire, car toute écriture dans une variable volatile établit une relation passe-avant avec les lectures suivantes de cette même variable.
Cela signifie que les modifications apportées à une variable volatile
sont toujours visibles par les autres threads. Cela signifie également que lorsqu'un thread lit une variable volatile, il voit non seulement la dernière modification apportée à la variable volatile
, mais également les effets secondaires du code qui a conduit à la modification.
Comme expliqué dans la réponse Peter Parker
, en l'absence de modificateur volatile
, la pile de chaque thread peut avoir sa propre copie de variable. En rendant la variable volatile
, les problèmes de cohérence de la mémoire ont été résolus.
Consultez la page jenkov tutorial pour une meilleure compréhension.
Jetez un coup d’œil à la question SE sur le sujet pour plus de détails sur les cas d’utilisation volatile:
Différence entre volatile et synchronisé en Java
Un cas d'utilisation pratique:
Vous avez plusieurs threads, qui doivent imprimer l'heure actuelle dans un format particulier, par exemple: Java.text.SimpleDateFormat("HH-mm-ss")
. Yon peut avoir une classe, qui convertit l'heure actuelle en SimpleDateFormat
et met à jour la variable toutes les secondes. Tous les autres threads peuvent simplement utiliser cette variable volatile pour imprimer l'heure actuelle dans les fichiers journaux.
Vous trouverez ci-dessous un code très simple pour démontrer l'exigence de volatile
.
// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
public static void main(String[] a) throws Exception {
Task task = new Task();
new Thread(task).start();
Thread.sleep(500);
long stoppedOn = System.nanoTime();
task.stop(); // -----> do this to stop the thread
System.out.println("Stopping on: " + stoppedOn);
}
}
class Task implements Runnable {
// Try running with and without 'volatile' here
private volatile boolean state = true;
private int i = 0;
public void stop() {
state = false;
}
@Override
public void run() {
while(state) {
i++;
}
System.out.println(i + "> Stopped on: " + System.nanoTime());
}
}
Lorsque volatile
n'est pas utilisé: vous ne verrez jamais le message 'Stopped on: xxx' même après 'Stopping on: xxx' et le programme continue de s'exécuter.
Stopping on: 1895303906650500
Lorsque volatile
est utilisé: , vous verrez immédiatement le message 'Arrêté: xxx'.
Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300
La clé volatile, lorsqu'elle est utilisée avec une variable, s'assurera que les threads lisant cette variable verront la même valeur. Maintenant, si vous avez plusieurs threads en lecture et en écriture sur une variable, rendre la variable volatile ne sera pas suffisant et les données seront corrompues. Les threads d'image ont lu la même valeur, mais chacun d'entre eux a fait quelques sauts (disons incrémenté d'un compteur). Lors de l'écriture dans la mémoire, l'intégrité des données est violée. C’est pourquoi il est nécessaire de synchroniser la varible (différentes manières sont possibles)
Si les modifications sont effectuées par 1 thread et que les autres doivent simplement lire cette valeur, le composant volatile sera approprié.
Une variable Volatile est modifiée de manière asynchrone par l'exécution simultanée de threads dans une application Java. Il n'est pas autorisé de disposer d'une copie locale d'une variable différente de la valeur actuellement conservée dans la mémoire "principale". En effet, une donnée déclarée volatile doit avoir ses données synchronisées sur tous les threads, de sorte que chaque fois que vous accédez à la variable ou que vous la mettez à jour dans un thread, tous les autres threads voient immédiatement la même valeur. Bien sûr, il est probable que les variables volatiles ont un accès plus lourd et une surcharge de mise à jour que les variables "ordinaires", car les threads de raison peuvent avoir leur propre copie de données pour une meilleure efficacité.
Lorsqu'un champ est déclaré volatil, le compilateur et le moteur d'exécution sont avertis que cette variable est partagée et que les opérations correspondantes ne doivent pas être réorganisées avec d'autres opérations en mémoire. processeurs, de sorte qu'une lecture d'une variable volatile renvoie toujours l'écriture la plus récente par un thread.
pour référence, reportez-vous à cette http://techno-terminal.blogspot.in/2015/11/what-are-volatile-variables.html
la variable volatile est généralement utilisée pour la mise à jour instantanée (vidage) dans la ligne principale du cache partagé une fois mise à jour, afin que les modifications soient immédiatement répercutées sur tous les threads de travail.
J'aime l'explication de Jenkov :
Le mot clé Java volatile est utilisé pour marquer une variable Java comme "en cours de stockage dans la mémoire principale". Plus précisément, cela signifie que chaque lecture d'une variable volatile sera lue dans la mémoire principale de l'ordinateur et non dans le cache du processeur, et que toute écriture dans une variable volatile sera écrite dans la mémoire principale et pas uniquement dans le cache du processeur .
En réalité, depuis Java 5, le mot clé volatile garantit plus que cela Les variables volatiles sont écrites et lues dans la mémoire principale.
Il s’agit d’une garantie de visibilité étendue appelée garantie sur la survenance.
Considérations de performance de volatile
La lecture et l'écriture de variables volatiles entraînent la lecture ou l'écriture de la variable dans la mémoire principale. Lire et écrire dans la mémoire principale coûte plus cher que d'accéder au cache du processeur. L'accès aux variables volatiles empêche également la réorganisation des instructions, qui est une technique d'amélioration des performances normale. Par conséquent, vous ne devez utiliser des variables volatiles que lorsque vous devez réellement renforcer la visibilité des variables.