Maison  >  Article  >  Java  >  Compréhension approfondie du mot clé volatile

Compréhension approfondie du mot clé volatile

(*-*)浩
(*-*)浩avant
2019-09-07 16:44:453068parcourir

Compréhension approfondie du mot clé volatile

Compréhension approfondie des mots-clés volatils

1.volatile et visibilité

Nous savons tous que le volatile peut garantir la visibilité, mais comment est-il garanti ?

Ceci est lié au principe Happen-before. La troisième disposition de ce principe est la suivante : pour une variable modifiée de manière volatile, l'opération d'écriture doit être antérieure à l'opération de lecture de la variable. Les étapes spécifiques sont les suivantes :

Le thread A lit les variables partagées dans la mémoire de travail, et le thread B lit également les variables partagées dans la mémoire de travail.

Une fois que le thread A a modifié la variable partagée, elle sera immédiatement actualisée dans la mémoire principale. À ce moment, la variable partagée dans la mémoire de travail du thread B sera définie comme non valide et la nouvelle valeur doit être modifiée. à relire depuis la mémoire principale. Répercuté sur le matériel, la ligne de cache du CPU est définie sur un état invalide.

Cela garantit la visibilité. En termes simples, après qu'un thread modifie la variable volatile modifiée et l'actualise dans la mémoire principale, cela invalidera les variables partagées dans la mémoire de travail des autres threads et devra être récupérée à partir du mémoire principale. Relisez ceci.

2. Volatil et ordre

Nous savons tous que le volatile peut garantir l'ordre, alors comment est-il garanti ?

volatile garantit l'ordre et est relativement simple. Il interdit à la JVM et au processeur de réorganiser les instructions pour les variables modifiées par le mot-clé volatile, mais les variables avant ou après la variable peuvent être ordonnées arbitrairement, du moment que le résultat final est obtenu. est le même que celui de la variable. Gardez simplement les résultats cohérents avant le changement.

Principe sous-jacent

Les variables modifiées par volatile seront préfixées par un "lock:" en bas. L'instruction avec le préfixe "lock" est équivalente à. une barrière à mémoire. C'est précisément la clé pour assurer la visibilité et l'ordre. Les principales fonctions de cette barrière sont les suivantes :

Lorsque les instructions sont réorganisées, le code devant la barrière ne peut pas être réorganisé derrière la barrière, ni derrière la barrière. le code derrière la barrière. Réorganiser devant la barrière.

Lors de l'exécution d'une barrière de mémoire, assurez-vous que tous les codes précédents ont été exécutés et que les résultats de l'exécution sont visibles pour le code derrière la barrière.

Forcer les variables de la mémoire de travail à être vidées vers la mémoire principale.

Les variables dans la mémoire de travail des autres threads seront définies comme invalides et devront être à nouveau lues à partir de la mémoire principale.

3. Volatil et atomicité

Nous savons tous que le volatile ne peut pas garantir l'atomicité, alors pourquoi ne peut-il pas garantir l'atomicité ?

Démonstration de code :

package com.github.excellent01;

import java.util.concurrent.CountDownLatch;

/**
 * @auther plg
 * @date 2019/5/19 9:37
 */
public class TestVolatile implements Runnable {
    private volatile Integer num = 0;
    private static CountDownLatch latch = new CountDownLatch(10);
    @Override
    public void run() {
        for(int i = 0; i < 1000; i++){
            num++;
        }
        latch.countDown();
    }

    public Integer getNum() {
        return num;
    }

    public static void main(String[] args) throws InterruptedException {
        TestVolatile test = new TestVolatile();
        for(int i = 0; i < 10; i++){
            new Thread(test).start();
        }
        latch.await();
        System.out.println(test.getNum());
    }
}

Démarrez 10 threads, chaque thread ajoute la variable partagée num 1000 fois, et lorsque tous les threads ont fini de s'exécuter, imprimez le résultat final de num.

Compréhension approfondie du mot clé volatile

Il y en a rarement 10 000. C'est parce que volatile ne peut pas garantir l'atomicité.

Analyse des causes :

L'opération de num++ se compose de trois étapes :

Lire num de la mémoire principale vers la mémoire de travail

Ajoutez-en un à la mémoire de travail

Une fois l'incrément terminé, réécrivez-le dans la mémoire principale.

Bien que ces trois étapes soient toutes des opérations atomiques, ensemble, elles ne sont pas des opérations atomiques, et chaque étape peut être interrompue pendant l'exécution.

Supposons que la valeur de num soit 10 à ce moment. Le thread A lit la variable dans sa propre mémoire de travail. À ce moment, un changement de CPU se produit également dans sa propre mémoire de travail. temps, la valeur est également 10. .Thread B modifie la valeur de num dans sa propre mémoire de travail et la change en 11, mais elle n'a pas été actualisée dans la mémoire principale pour le moment, donc le thread A ne sait pas que la valeur de num a changé. Comme mentionné précédemment, une fois la variable volatile modifiée, les autres threads le connaîtront immédiatement. La condition préalable est qu'elle soit d'abord actualisée dans la mémoire principale. À ce moment-là, les autres threads définiront la valeur du partage. variable sur laquelle ils travaillent est invalide. Parce qu'il n'a pas été actualisé dans la mémoire principale, A ne le savait pas bêtement et a ajouté un à 10. Par conséquent, bien que les deux threads aient finalement effectué une opération d'incrémentation, le résultat final n'a été ajouté qu'une seule fois.

C'est pourquoi volatile ne peut pas garantir l'atomicité.

Scénarios d'utilisation de volatile

Selon les caractéristiques de volatile, l'ordre et la visibilité sont garantis, mais l'atomicité n'est pas garantie, donc volatile peut être utilisé pour ceux qui le font ne nécessite pas d'atomicité , ou lorsque l'atomicité a été garantie :

Démonstration de code

volatile boolean shutdownRequested 
public void shutdown() {
  shutdownRequested = true;
}
public void work() {
  while(shutdownRequested) {
    //do stuff
 }
}

Tant que le thread modifie shutdownRequested, le thread exécutant le travail le verra immédiatement, donc il s'arrêtera immédiatement. Sinon, si volatile est ajouté, ce sera toujours vrai à chaque fois qu'il lira des données de la mémoire de travail et sera exécuté en continu sans savoir que quelqu'un d'autre l'a arrêté.

Démonstration du code :

package com.github.excellent;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * 启动线程会被阻塞,flag 从内存读入,会存入寄存器中,下次直接从寄存器取值
 * 因此值一直是false
 * 即使别的线程已经将值更改了,它也不知道
 * 加volatile即可。也可以加锁,只要保证内存可见性即可
 * @auther plg
 * @date 2019/5/2 22:40
 */
public class Testvolatile {
    public static  boolean flag = false;
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            for(;;) {
                System.out.println(flag);
            }
        });
        Thread thread2 = new Thread(()->{
            for(;;){
                flag = true;
            }
        });
        thread1.start();
        Thread.sleep(1000);
        thread2.start();
    }
}

Résultat de l'exécution :

Compréhension approfondie du mot clé volatile

C'est tellement stupide que d'autres l'ont modifié sans le savoir, et il sort toujours. FAUX. Ajoutez simplement un volatile et tout ira bien.

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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer