volatile est un mot-clé relativement important en Java. Il est principalement utilisé pour modifier des variables qui seront accédées et modifiées par différents threads.
Et cette variable ne peut garantir que deux caractéristiques, l'une est d'assurer l'ordre et l'autre est d'assurer la visibilité.
Alors, qu’est-ce que l’ordre et qu’est-ce que la visibilité ?
Alors, qu'est-ce que l'ordre ?
En fait, l'ordre d'exécution du programme est basé sur l'ordre du code, et la réorganisation des instructions est interdite.
Cela semble naturel, mais ce n'est pas le cas. La réorganisation des instructions permet à la JVM d'optimiser les instructions, d'améliorer l'efficacité de l'exécution du programme et d'augmenter le parallélisme autant que possible sans affecter les résultats d'exécution des programmes monothread.
Mais dans l'environnement 多线程
, l'ordre de certains codes change, ce qui peut provoquer des erreurs logiques.
Et volatile est bien connu en raison de cette fonctionnalité.
Comment volatile assure-t-il l'ordre ?
De nombreux amis disent que ce qui est dit sur Internet, c'est que volatile peut interdire la réorganisation des instructions, ce qui garantit que le programme de code sera exécuté strictement dans l'ordre du code. Cela garantit l’ordre. Les opérations sur les variables modifiées par volatile seront exécutées strictement dans l'ordre du code, c'est-à-dire que lorsque le code est exécuté sur la variable modifiée par volatile, le code précédent doit être exécuté, et le code suivant ne doit pas être exécuté. .
Si l'intervieweur ne continue pas à creuser plus profondément à ce moment-là, alors félicitations, cette question a peut-être trouvé une réponse, mais si l'intervieweur continue de creuser plus profondément, pourquoi le réarrangement des commandes est-il interdit ?指 L'exécution de l'instruction du code source à l'instruction est généralement divisée en trois types de réorganisation, comme le montre la figure :
Nous devons voir comment Volatile interdit le réarrangement de l'instruction.
Nous utilisons directement le code pour vérifier :
public class ReSortDemo { int a = 0; boolean flag = false; public void mehtod1(){ a = 1; flag = true; } public void method2(){ if(flag){ a = a +1; System.out.println("最后的值: "+a); } } }Si quelqu'un voit ce code, il dira certainement, quel sera le résultat de ce code ? Certaines personnes disent que c'est 2. Oui, si vous l'appelez uniquement dans un seul thread, le résultat est 2, mais s'il est appelé dans plusieurs threads, le résultat final peut ne pas être le 2 que nous imaginions pour le moment. , nous devons changer. Les deux variables sont définies comme volatiles. Si vous en savez plus sur le mode singleton, vous avez dû faire attention à ce volatile Pourquoi ?
Jetons un coup d'œil au code suivant :
class Singleton { // 不是一个原子性操作 //private static Singleton instance; //改进,Volatile 可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生! private static volatile Singleton instance; // 构造器私有化 private Singleton() { } // 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题,同时保证了效率, 推荐使用 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }Connaissez-vous le modèle singleton ci-dessus ?
Oui, c'est **une double vérification (style paresseux DCL) **
Certaines personnes diront qu'en raison de l'existence de la réorganisation des instructions, le mécanisme de récupération à double extrémité n'est pas nécessairement thread-safe, oui, donc le Le mot-clé synchronisé est utilisé pour le rendre thread-safe. VisibilitéEn fait, la visibilité est la question de savoir si les modifications apportées aux variables partagées sont immédiatement visibles par les autres threads dans un environnement multithread. Alors, où apparaît généralement sa visibilité ? Où est-il utilisé ? En fait, cette variable est généralement utilisée, principalement pour assurer sa visibilité. Par exemple, une variable globale est définie, et il y a une boucle pour juger de la valeur de cette variable. Lorsqu'un thread modifie ce paramètre, cette boucle. Il s'arrêtera et passera à l'exécution suivante.Jetons un coup d'œil à l'implémentation du code sans modification volatile :
public class Test { private static boolean flag = false; public static void main(String[] args) throws Exception{ new Thread(new Runnable() { @Override public void run() { System.out.println("线程A开始执行:"); for (;;){ if (flag){ System.out.println("跳出循环"); break; } } } }).start(); Thread.sleep(100); new Thread(new Runnable() { @Override public void run() { System.out.println("线程B开始执行"); flag = true; System.out.println("标识已经变更"); } }).start(); } }Le résultat est comme vous pouvez l'imaginer,
Le résultat de l'exécution doit être :
Le thread A commence à s'exécuter :
Le thread B commence à s'exécuterLe logo a été modifié
En effet, c'est tout.
Et si nous utilisons volatile, alors le résultat de l'exécution de ce code sera différent ?
Essayons :
public class Test { private static volatile boolean flag = false; public static void main(String[] args) throws Exception{ new Thread(new Runnable() { @Override public void run() { System.out.println("线程A开始执行:"); for (;;){ if (flag){ System.out.println("跳出循环"); break; } } } }).start(); Thread.sleep(100); new Thread(new Runnable() { @Override public void run() { System.out.println("线程B开始执行"); flag = true; System.out.println("标识已经变更"); } }).start(); }De cette façon, nous pouvons voir un autre résultat d'exécution et l'instruction de sortie dans la boucle peut être exécutée.
En d'autres termes, dans le thread B, nous modifions cette variable modifiée, et enfin, dans le thread A, nos informations de données peuvent être lues en douceur.
L'atomicité peut-elle être garantie ? Non, jetons un coup d'œil à du code, des variables modifiées par volatile,public class Test { // volatile不保证原子性 // 原子性:保证数据一致性、完整性 volatile int number = 0; public void addPlusPlus() { number++; } public static void main(String[] args) { Test volatileAtomDemo = new Test(); for (int j = 0; j < 20; j++) { new Thread(() -> { for (int i = 0; i < 1000; i++) { volatileAtomDemo.addPlusPlus(); } }, String.valueOf(j)).start(); }// 后台默认两个线程:一个是main线程,一个是gc线程 while (Thread.activeCount() > 2) { Thread.yield(); } // 如果volatile保证原子性的话,最终的结果应该是20000 // 但是每次程序执行结果都不等于20000 System.out.println(Thread.currentThread().getName() + " final number result = " + volatileAtomDemo.number); } }
résultat du numéro final principal = 17114résultat du numéro final principal = 20000résultat du numéro final principal = 19317
Trois exécutions, toutes avec des résultats différents,
Pourquoi cela se produit-il ? Cela a quelque chose à voir avec le nombre++number++ est divisé en 3 instructions
Exécutez GETFIELD pour obtenir le numéro de valeur d'origine dans la mémoire principale
Exécutez IADD pour ajouter 1 opération
Exécutez PUTFIELD pour réécrire la valeur dans la mémoire de travail dans le mémoire principale中
Lorsque plusieurs threads exécutent l'instruction PUTFIELD simultanément, il y aura un problème d'écrasement de la mémoire principale de réécriture, donc le résultat final n'est pas 20000, donc volatile ne peut pas garantir l'atomicité.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!