web-dev-qa-db-fra.com

Exemple le plus simple et le plus compréhensible de mot clé volatile en java

Je lis sur le mot-clé volatile en Java et je comprends parfaitement la théorie.

Mais ce que je recherche, c’est un bon exemple de cas qui montre ce qui se produirait si la variable n’était pas volatile et si c’était le cas.

L'extrait de code ci-dessous ne fonctionne pas comme prévu ( de aioobe )

class Test extends Thread {

    boolean keepRunning = true;

    public void run() {
        while (keepRunning) {
        }

        System.out.println("Thread terminated.");
    }

    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println("keepRunning set to false.");
    }
}

Idéalement, si keepRunning n'était pas volatile, le thread devrait continuer à fonctionner indéfiniment. Mais, cela s'arrête après quelques secondes.

J'ai deux questions de base: -

  • Quelqu'un peut-il expliquer volatile avec exemple? Pas avec la théorie de JLS.
  • Est-ce que la substitution volatile pour la synchronisation? At-il atteindre l'atomicité?
58
tmgr

Volatile -> Garantit la visibilité et PAS l'atomicité

Synchronisation (verrouillage) -> Garantit la visibilité et l'atomicité (si cela est fait correctement)

Volatile ne remplace pas la synchronisation

Utilisez volatile uniquement lorsque vous mettez à jour la référence et n'effectuez pas d'autres opérations dessus.

Exemple:

volatile int i = 0;

public void incrementI(){
   i++;
}

ne sera pas thread-safe sans l'utilisation de la synchronisation ou AtomicInteger car l'incrémentation est une opération composée.

Pourquoi le programme ne fonctionne pas indéfiniment?

Cela dépend de diverses circonstances. Dans la plupart des cas, la machine virtuelle Java est suffisamment intelligente pour vider le contenu.

Utilisation correcte de volatile discute des diverses utilisations possibles de volatile. Utiliser correctement volatile est délicat, je dirais "En cas de doute, laissez tomber", utilisez plutôt un bloc synchronisé.

Également:

Le bloc synchronized peut être utilisé à la place de volatile mais l'inverse n'est pas vrai

45
Narendra Pathai

Pour votre exemple particulier: si elle n'est pas déclarée volatile, la machine virtuelle Java du serveur pourrait extraire la variable keepRunning de la boucle car elle n'est pas modifiée in la boucle (la transformant en une boucle infinie), mais la machine virtuelle client . C'est pourquoi vous voyez des résultats différents.

Les explications générales sur les variables volatiles sont les suivantes:

Lorsqu'un champ est déclaré volatile, le compilateur et le moteur d'exécution sont avertis que cette variable est partagée et que les opérations qu'il contient ne doivent pas être réorganisées avec d'autres opérations en mémoire. Les variables volatiles ne sont pas mises en cache dans les registres ni dans les caches où elles sont cachées des autres processeurs Donc, la lecture d'une variable volatile renvoie toujours l'écriture la plus récente par un thread}. 

Les effets de visibilité des variables volatiles vont au-delà de la valeur de la variable volatile elle-même. Lorsque le thread A écrit dans une variable volatile et que B ensuite lit cette même variable, les valeurs de toutes les variables visibles par A avant l'écriture dans la variable volatile deviennent visibles par B après la lecture de la variable volatile

L'utilisation la plus courante des variables volatiles est un indicateur d'achèvement, d'interruption ou d'état:

  volatile boolean flag;
  while (!flag)  {
     // do something untill flag is true
  }

Les variables volatiles peuvent être utilisées pour d'autres types d'informations d'état, mais vous devez faire preuve de plus de prudence lorsque vous tentez cette opération. Par exemple, la sémantique de volatile n'est pas assez forte pour rendre l'opération d'incrémentation (count++) atomique, à moins que vous ne puissiez garantir que la variable est écrite uniquement à partir d'un seul thread.

_ {Le verrouillage peut garantir à la fois visibilité et atomicité; les variables volatiles ne peuvent garantir que la visibilité.}

Vous pouvez utiliser des variables volatiles uniquement lorsque tous les critères suivants sont remplis: 

  • Les écritures dans la variable ne dépendent pas de sa valeur actuelle, sinon vous pouvez vous assurer qu'un seul thread met à jour la valeur;
  • La variable ne participe pas aux invariants avec d'autres variables d'état; et
  • Le verrouillage n’est requis pour aucune autre raison pendant l’accès à la variable.

Astuce de débogage _: veillez à toujours spécifier le commutateur de ligne de commande JVM -server lors de l'appel de la JVM, même pour le développement et les tests. La machine virtuelle Java du serveur effectue davantage d’optimisation que la machine virtuelle client, par exemple en extrayant des variables d’une boucle qui ne sont pas modifiées dans la boucle; Le code qui peut sembler fonctionner dans l'environnement de développement (la machine virtuelle Java cliente) peut être endommagé dans l'environnement de déploiement (machine virtuelle JVM du serveur).

Ceci est extrait de Java Concurrency in Practice, le meilleur livre que vous puissiez trouver sur ce sujet.

23
Sokolov

J'ai légèrement modifié votre exemple. Utilisez maintenant l'exemple avec keepRunning en tant que membre volatile et non volatile:

class TestVolatile extends Thread{
    //volatile
    boolean keepRunning = true;

    public void run() {
        long count=0;
        while (keepRunning) {
            count++;
        }

        System.out.println("Thread terminated." + count);
    }

    public static void main(String[] args) throws InterruptedException {
        TestVolatile t = new TestVolatile();
        t.start();
        Thread.sleep(1000);
        System.out.println("after sleeping in main");
        t.keepRunning = false;
        t.join();
        System.out.println("keepRunning set to " + t.keepRunning);
    }
}
13
paritosht

Quel est le mot clé volatile?

volatile mot clé empêche caching of variables.

Considérons le code, d'abord sans volatile mot clé

class MyThread extends Thread {
    private boolean running = true;   //non-volatile keyword

    public void run() {
        while (running) {
            System.out.println("hello");
        }
    }

    public void shutdown() {
        running = false;
    }
}

public class Main {

    public static void main(String[] args) {
        MyThread obj = new MyThread();
        obj.start();

        Scanner input = new Scanner(System.in);
        input.nextLine(); 
        obj.shutdown();   
    }    
}

Idéalement , ce programme devrait print hello jusqu'à ce que vous appuyiez sur RETURN key. Mais sur some machines, il peut arriver que la variable running soit cached et que vous ne puissiez pas changer sa valeur à partir de la méthode shutdown (), ce qui entraînerait une impression infinite de texte hello.

Ainsi, en utilisant le mot clé volatile, il est guaranteed que votre variable ne soit pas mise en cache, c'est-à-dire que run fine sur all machines

private volatile boolean running = true;  //volatile keyword

Ainsi, l’utilisation du mot clé volatile est une good et safer programming practice.

9
Prateek Joshi

Variable Volatile : Le mot clé volatile est applicable aux variables. Le mot clé volatile en Java garantit que la valeur de la variable volatile sera toujours lue dans la mémoire principale et non dans le cache local de Thread.

Access_Modifier volatile DataType Variable_Name;

Champ volatile: Indique à la VM que plusieurs threads peuvent essayer d'accéder/mettre à jour la valeur du champ en même temps. À un type spécial de variables d'instance qui doit être partagé entre tous les threads avec une valeur modifiée. Semblable à la variable statique (Classe), une seule copie de la valeur volatile est mise en cache dans la mémoire principale. Ainsi, avant d'exécuter une opération ALU, chaque thread doit lire la valeur mise à jour dans la mémoire principale après l'opération ALU et l'écrire directement dans la mémoire principale. (Une écriture dans une variable volatile v est synchronisée avec toutes les lectures suivantes de v par un thread quelconque) Cela signifie que les modifications apportées à une variable volatile sont toujours visibles par les autres threads.

 enter image description here

Ici, un nonvoltaile variable si Thread t1 change la valeur dans le cache de t1, Thread t2 ne peut pas accéder à la valeur modifiée avant que t1 n’écrit, t2 est lu dans la mémoire principale pour la dernière valeur modifiée, ce qui peut conduire à Data-Inconsistancy.

volatile ne peut pas être mis en cache - assembleur

    +--------------+--------+-------------------------------------+
    |  Flag Name   |  Value | Interpretation                      |
    +--------------+--------+-------------------------------------+
    | ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached.|
    +--------------+--------+-------------------------------------+
    |ACC_TRANSIENT | 0x0080 | Declared transient; not written or  |
    |              |        | read by a persistent object manager.|
    +--------------+--------+-------------------------------------+

Shared Variables : La mémoire pouvant être partagée entre les threads est appelée mémoire partagée ou mémoire heap. Tous les champs d'instance, les champs statiques et les éléments de tableau sont stockés dans la mémoire de tas.

Synchronisation : synchronized est applicable aux méthodes, aux blocs. permet d'exécuter uniquement 1 thread à la fois sur un objet. Si t1 prend le contrôle, les threads restants doivent attendre jusqu'à ce qu'il relâche le contrôle.

Exemple:

public class VolatileTest implements Runnable {

    private static final int MegaBytes = 10241024;

    private static final Object counterLock = new Object();
    private static int counter = 0;
    private static volatile int counter1 = 0;

    private volatile int counter2 = 0;
    private int counter3 = 0;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            concurrentMethodWrong();
        }

    }

    void addInstanceVolatile() {
        synchronized (counterLock) {
            counter2 = counter2 + 1;
            System.out.println( Thread.currentThread().getName() +"\t\t « InstanceVolatile :: "+ counter2);
        }
    }

    public void concurrentMethodWrong() {
        counter = counter + 1;
        System.out.println( Thread.currentThread().getName() +" « Static :: "+ counter);
        sleepThread( 1/4 );

        counter1 = counter1 + 1;
        System.out.println( Thread.currentThread().getName() +"\t « StaticVolatile :: "+ counter1);
        sleepThread( 1/4 );

        addInstanceVolatile();
        sleepThread( 1/4 );

        counter3 = counter3 + 1;
        sleepThread( 1/4 );
        System.out.println( Thread.currentThread().getName() +"\t\t\t\t\t « Instance :: "+ counter3);
    }
    public static void main(String[] args) throws InterruptedException {
        Runtime runtime = Runtime.getRuntime();

        int availableProcessors = runtime.availableProcessors();
        System.out.println("availableProcessors :: "+availableProcessors);
        System.out.println("MAX JVM will attempt to use : "+ runtime.maxMemory() / MegaBytes );
        System.out.println("JVM totalMemory also equals to initial heap size of JVM : "+ runtime.totalMemory() / MegaBytes );
        System.out.println("Returns the amount of free memory in the JVM : "+ untime.freeMemory() / MegaBytes );
        System.out.println(" ===== ----- ===== ");

        VolatileTest volatileTest = new VolatileTest();
        Thread t1 = new Thread( volatileTest );
        t1.start();

        Thread t2 = new Thread( volatileTest );
        t2.start();

        Thread t3 = new Thread( volatileTest );
        t3.start();

        Thread t4 = new Thread( volatileTest );
        t4.start();

        Thread.sleep( 10 );;

        Thread optimizeation = new Thread() {
            @Override public void run() {
                System.out.println("Thread Start.");

                Integer appendingVal = volatileTest.counter2 + volatileTest.counter2 + volatileTest.counter2;

                System.out.println("End of Thread." + appendingVal);
            }
        };
        optimizeation.start();
    }

    public void sleepThread( long sec ) {
        try {
            Thread.sleep( sec * 1000 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Statique [Class Field] vs volatile [Instance Field] - les deux ne sont pas mis en cache par les threads

  • Les champs statiques sont communs à tous les threads et sont stockés dans la zone de méthode. Statique avec volatile sans utilisation. Le champ statique ne peut pas être sérialisé.

  • Volatile principalement utilisé avec une variable d'instance qui est stockée dans la zone de tas. La principale utilisation de volatile est de maintenir la valeur mise à jour sur tous les threads. Le champ d'instance volatile peut être Sérialisé .

@voir

4
Yash

Lorsqu'une variable est volatile, elle garantit qu'elle ne sera pas mise en cache et que différents threads verront la valeur mise à jour. Cependant, ne pas le marquer volatile ne garantit pas l’opposition. volatile était l'une de ces choses qui était cassée dans la JVM pendant longtemps et qui n'était toujours pas bien comprise.

3
Jeff Storey

Idéalement, si keepRunning n'était pas volatile, le thread devrait continuer à fonctionner indéfiniment. Mais, cela s'arrête après quelques secondes.

Si vous utilisez un seul processeur ou si votre système est très occupé, il est possible que le système d'exploitation permute les threads, ce qui entraîne certains niveaux d'invalidation du cache. Comme d'autres l'ont mentionné, ne pas avoir une volatile ne signifie pas que la mémoire sera non pas partagée, mais que la JVM tente de ne pas synchroniser la mémoire si elle le peut pour des raisons de performances, afin de ne pas la mettre à jour.

Une autre chose à noter est que System.out.println(...) est synchronisé, car la PrintStream sous-jacente effectue la synchronisation pour arrêter les sorties qui se chevauchent. Donc, vous obtenez la synchronisation de mémoire "gratuitement" dans le thread principal. Cela n'explique toujours pas pourquoi la boucle de lecture voit les mises à jour du tout.

Que les lignes println(...) soient entrées ou sorties, votre programme tourne pour moi sous Java6 sur un MacBook Pro équipé d’un processeur Intel i7.

Quelqu'un peut-il expliquer volatile avec exemple? Pas avec la théorie de JLS.

Je pense que votre exemple est bon. Vous ne savez pas pourquoi cela ne fonctionne pas avec toutes les instructions System.out.println(...) supprimées. Ça marche pour moi.

Est-ce que la substitution volatile pour la synchronisation? At-il atteindre l'atomicité?

En termes de synchronisation de la mémoire, volatile lève les mêmes barrières de mémoire qu’un bloc synchronized à la différence près que la barrière volatile est unidirectionnelle ou bidirectionnelle. volatile lectures lèvent une barrière de charge tandis que les écritures lèvent une barrière de magasin. Un bloc synchronized est une barrière bidirectionnelle.

En termes de atomicity, cependant, la réponse est "cela dépend". Si vous lisez ou écrivez une valeur dans un champ, volatile fournit l'atomicité appropriée. Toutefois, l'incrémentation d'un champ volatile souffre de la limitation selon laquelle ++ correspond en réalité à 3 opérations: lecture, incrémentation, écriture. Dans ce cas ou dans les cas de mutex plus complexes, un bloc synchronized complet peut être nécessaire.

2
Gray

volatile ne va pas nécessairement créer des changements géants, selon la machine virtuelle Java et le compilateur. Cependant, dans de nombreux cas (Edge), il peut y avoir une différence entre l'optimisation empêchant de remarquer les modifications d'une variable, par opposition à leur écriture correcte.

Fondamentalement, un optimiseur peut choisir de placer des variables non volatiles sur des registres ou sur la pile. Si un autre thread les modifie dans le tas ou dans les primitives des classes, l'autre thread continuera à le rechercher dans la pile et il sera périmé.

volatile garantit que de telles optimisations ne se produisent pas et que toutes les lectures et écritures se font directement dans le tas ou à un autre endroit où toutes les discussions le verront.

2
Andrey Akhmetov

S'il vous plaît trouver la solution ci-dessous,

La valeur de cette variable ne sera jamais mise en cache par les threads localement: toutes les lectures et écritures iront directement à "mémoire principale". Le volatile force le thread à mettre à jour la variable d'origine à chaque fois. 

public class VolatileDemo {

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {

        ChangeMaker changeMaker = new ChangeMaker();
        changeMaker.start();

        ChangeListener changeListener = new ChangeListener();
        changeListener.start();

    }

    static class ChangeMaker extends Thread {

        @Override
        public void run() {
            while (MY_INT < 5){
                System.out.println("Incrementing MY_INT "+ ++MY_INT);
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException exception) {
                    exception.printStackTrace();
                }
            }
        }
    }

    static class ChangeListener extends Thread {

        int local_value = MY_INT;

        @Override
        public void run() {
            while ( MY_INT < 5){
                if( local_value!= MY_INT){
                    System.out.println("Got Change for MY_INT "+ MY_INT);
                    local_value = MY_INT;
                }
            }
        }
    }

}

Veuillez consulter ce lien http://Java.dzone.com/articles/Java-volatile-keyword-0 pour plus de clarté.

1
Azhaguvel A

Les objets déclarés volatils sont généralement utilisés pour communiquer des informations d'état entre les threads. Pour garantir la mise à jour des caches de la CPU, c'est-à-dire, ils sont synchronisés, en présence de champs volatiles, d'une instruction de la CPU, d'une barrière de mémoire, souvent appelée membre ou fence, est émis pour mettre à jour les caches de la CPU avec une modification de la valeur d’un champ volatile.

Le modificateur volatile indique au compilateur que la variable modifiée par volatile peut être modifiée de manière inattendue par d'autres parties de votre programme.

La variable volatile doit être utilisée uniquement dans le contexte de thread. voir l'exemple ici

0
user2554822

Le mot clé volatile indique à la machine virtuelle Java qu'il peut être modifié par un autre thread . Chaque thread a sa propre pile et donc sa propre copie des variables auxquelles il peut accéder. Lorsqu'un thread est créé, il copie la valeur de toutes les variables accessibles dans sa propre mémoire. 

public class VolatileTest {
    private static final Logger LOGGER = MyLoggerFactory.getSimplestLogger();

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {
        new ChangeListener().start();
        new ChangeMaker().start();
    }

    static class ChangeListener extends Thread {
        @Override
        public void run() {
            int local_value = MY_INT;
            while ( local_value < 5){
                if( local_value!= MY_INT){
                    LOGGER.log(Level.INFO,"Got Change for MY_INT : {0}", MY_INT);
                     local_value= MY_INT;
                }
            }
        }
    }

    static class ChangeMaker extends Thread{
        @Override
        public void run() {

            int local_value = MY_INT;
            while (MY_INT <5){
                LOGGER.log(Level.INFO, "Incrementing MY_INT to {0}", local_value+1);
                MY_INT = ++local_value;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }
    }
}

essayez cet exemple avec et sans volatile.

0

Beaucoup d'excellents exemples, mais je veux juste ajouter qu'il existe un certain nombre de scénarios dans lesquels volatile est requis, de sorte qu'il n'existe aucun exemple concret pour les gouverner a.

  1. Vous pouvez utiliser volatile pour forcer tous les threads à obtenir la dernière valeur de la variable à partir de la mémoire principale.
  2. Vous pouvez utiliser synchronization pour protéger des données critiques
  3. Vous pouvez utiliser Lock API
  4. Vous pouvez utiliser Atomic variables

Vérifiez-le pour plus exemples volatiles Java .

0
Johnny