Avez-vous déjà utilisé le mot-clé volatile en Java?


Au travail aujourd'hui, je suis tombé sur le mot-clé volatile en Java. N'étant pas familier avec elle, j'ai trouvé cette explication:

Java théorie et pratique: Gérer la volatilité

Étant donné le détail dans lequel cet article explique le mot-clé en question, l'utilisez-vous jamais ou pourriez-vous voir un cas dans lequel vous pourriez utiliser ce mot-clé de la manière correcte?

Author: Daniel Werner, 2008-09-20

21 answers

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 (autres threads en particulier) après 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'il teste la valeur après l'exécution de la méthode stop.

Le livre "Java de la Simultanéité dans la Pratique," que je recommande fortement, donne une bonne explication de volatile. Ce livre est écrit par la même personne qui a écrit l'article IBM qui est référencé dans la question (en fait, il cite son livre au bas de cet article). Mon utilisation de volatile est ce que son article appelle le "drapeau d'état du modèle 1."

Si vous voulez en savoir plus sur comment volatile fonctionne sous le capot, lire sur les la mémoire Java modèle. Si vous voulez aller au-delà de ce niveau, consultez un bon livre d'architecture informatique comme Hennessy & Patterson et lisez à propos de la cohérence du cache et de la cohérence du cache.

 632
Author: Greg Mattes, 2017-05-11 07:44:40

"... le modificateur volatile garantit que tout thread qui lit un champ verra la valeur écrite la plus récente." - Josh Bloch

Si vous envisagez d'utiliser volatile, lisez le paquet java.util.concurrent qui traite du comportement atomique.

Le message Wikipedia sur un Modèle Singleton montre volatile en cours d'utilisation.

 148
Author: Andrew Turner, 2017-05-11 07:43:16

Point important sur volatile:

  1. La synchronisation en Java est possible en utilisant les mots clés Java synchronized et volatile et les verrous.
  2. En Java, on ne peut pas avoir de variable 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 demandera aux threads JVM de lire la valeur de la variable volatile à partir de la mémoire principale et de ne pas la mettre en cache localement.
  3. Si une variable n'est pas partagée entre plusieurs threads alors il n'est pas nécessaire d'utiliser le volatile clé.

Source

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 une instance paresseusement au moment où la première requête arrive.

Si nous ne faisons pas la variable _instance volatile alors le Thread qui crée l'instance de Singleton ne peut pas communiquer avec l'autre thread. Donc, si le thread A crée un Singleton instance et juste après la création, le CPU corrompt, etc., tous les autres threads ne pourront pas voir la valeur de _instance comme non nulle et ils croiront qu'elle est toujours assignée à null.

Pourquoi cela arrive-t-il? Parce que les threads de lecture ne font aucun verrouillage et jusqu'à ce que le thread d'écriture sorte 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 tel les mises à jour seront visibles par tous les threads de lecture.

Conclusion: volatile mot-clé 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 est défini avant que le constructeur 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 volatiles ne sont visibles par les autres threads qu'une fois que le constructeur de l'objet a complètement terminé son exécution.
Source

entrez la description de l'image ici

volatile utilisation dans Java :

Les itérateurs rapides à l'échec sont typiquement implémentés à l'aide d'un compteur volatile sur l'objet list.

  • Lorsque la liste est mise à jour, le compteur est incrémenté.
  • Lorsqu'un Iterator est créé, la valeur courante du compteur est intégré dans le Iterator objet.
  • Lorsqu'une opération Iterator est effectuée, la méthode compare les deux valeurs de compteur et lance un ConcurrentModificationException si elles sont différentes.

La mise en œuvre de la sécurité intégrée iterators est généralement léger. 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'existe pas de modèle général.

 83
Author: Premraj, 2018-06-26 15:45:27

Volatile est très utile pour arrêter les threads.

Non pas que vous devriez écrire vos propres threads, Java 1.6 a beaucoup de beaux pools de threads. Mais si vous êtes sûr d'avoir besoin d'un fil, vous devrez savoir comment l'arrêter.

Le modèle que j'utilise pour les threads 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
  }
}

Notez qu'il n'y a pas besoin de synchronisation

 44
Author: Pyrolistical, 2008-09-24 22:29:39

Un exemple courant d'utilisation de volatile consiste à utiliser une variable volatile boolean comme indicateur pour terminer un thread. Si vous avez démarré un thread et que vous souhaitez pouvoir l'interrompre en toute sécurité à partir d'un thread différent, vous pouvez demander au thread de vérifier périodiquement un indicateur. Pour l'arrêter, définissez le drapeau sur true. En faisant l'indicateur volatile, vous pouvez vous assurer que le thread qui le vérifie verra qu'il a été défini la prochaine fois qu'il le vérifie sans même avoir à utiliser un bloc synchronized.

 28
Author: Dave L., 2008-09-20 04:00:01

Oui, volatile doit être utilisé chaque fois que vous voulez qu'une variable mutable soit accessible par plusieurs threads. Ce n'est pas un cas d'utilisation très courant car vous devez généralement effectuer plus d'une seule opération atomique (par exemple, vérifier l'état de la variable avant de la modifier), auquel cas vous utiliseriez un bloc synchronisé à la place.

 12
Author: ykaganovich, 2008-09-20 04:26:30

Personne n'a mentionné le traitement des opérations de lecture et d'écriture pour le type de variable longue et double. 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

 12
Author: Donatello Boccaforno, 2015-02-11 14:51:29

OMI deux scénarios importants autres que l'arrêt du thread dans lequel le mot-clé volatile est utilisé sont

  1. Mécanisme de verrouillage à double contrôle . Utilisé souvent dans la conception Singleton modèle. Dans ce singleton object needs to be declared volatile.
  2. Faux réveils . Le thread peut parfois se réveiller d'un appel d'attente même si aucun appel de notification n'a été émis. Ce comportement est appelé réveil supurious. Cela peut être contré en utilisant une variable conditionnelle(indicateur booléen). Mettez l'appel wait () dans une boucle while comme longtemps que le drapeau est vrai. Donc, si le thread se réveille de l'appel d'attente pour des raisons autres que notify/notifyall, il rencontre que l'indicateur est toujours vrai et appelle donc à nouveau wait. Avant d'appeler notify, définissez cet indicateur sur true. Dans ce cas, le boolean flag is declared as volatile.
 9
Author: Aniket Thakur, 2014-02-25 09:17:27

Une variable déclarée avec le mot-clé volatile, a deux qualités principales qui la rendent spéciale.

  1. Si nous avons une variable volatile, elle ne peut pas être mise en cache dans la mémoire cache de l'ordinateur(microprocesseur) par un thread. L'accès s'est toujours produit à partir de la mémoire principale.

  2. Si il y a un opération d'écriture aller sur une variable volatile, et tout à coup une opération de lecture est demandé, il est garanti que l'opération d'écriture sera terminé avant l'opération de lecture.

Deux qualités ci-dessus en déduisent que

  • Tous les threads lisant une variable volatile liront certainement la dernière valeur. Parce qu'aucune valeur mise en cache ne peut la polluer. De plus, la demande de lecture ne sera accordée qu'après l'achèvement de l'opération d'écriture en cours.

Et d'autre part,

  • Si nous étudions plus en détail le #2 que j'ai mentionné, nous pouvons voir que le mot-clé volatile est un moyen idéal de maintenir une variable partagée qui a 'n' nombre de threads en lecture et un seul thread en écriture pour y accéder. Une fois que nous avons ajouté le mot-clé volatile, c'est fait. Pas d'autres frais généraux sur la sécurité du filetage.

Inversement,

Nous ne peut pas faire usage de volatile clé uniquement, pour satisfaire à une variable partagée qui a plus d'une écriture threads accèdent.

 8
Author: Supun Wijerathne, 2018-06-23 02:28:48

Vous devrez utiliser le mot-clé 'volatile', ou 'synchronized' et tout autre outil et technique de contrôle de concurrence que vous pourriez avoir à votre disposition si vous développez une application multithread. Exemple d'une telle application est des applications de bureau.

Si vous développez une application qui serait déployée sur le serveur d'applications (Tomcat, JBoss AS, Glassfish, etc.), vous n'avez pas à gérer vous-même le contrôle de concurrence tel qu'il a déjà été traité par le serveur d'applications. En fait, si je mémorisé correctement, le standard Java EE interdit tout contrôle de concurrence dans les servlets et les EJB, car il fait partie de la couche "infrastructure" que vous deviez libérer de sa gestion. Vous ne contrôlez la concurrence dans une telle application que si vous implémentez des objets singleton. Cela a même déjà été résolu si vous tricotez vos composants à l'aide de frameworkd comme Spring.

Donc, dans la plupart des cas de développement Java où l'application est une application Web et utilise le framework IoC comme Spring ou EJB, vous n'auriez pas besoin d'utiliser "volatile".

 5
Author: Rudi Adianto, 2012-07-10 18:02:44

volatile garantit seulement que tous les threads, même eux-mêmes, incrémentent. Par exemple: un compteur voit la même face de la variable en même temps. Il n'est pas utilisé à la place de synchronisé ou atomique ou d'autres choses, 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 à la fois.

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 des résultats volatils ou non seront toujours différents. Mais si vous utilisez AtomicInteger ci-dessous les résultats seront toujours les mêmes. C'est la même chose 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);
            }
        }
    }
 5
Author: fatih tekin, 2016-08-18 15:06:14

Oui, je l'utilise beaucoup - il peut être très utile pour le code multithread. L'article que vous avez souligné est un bon. Bien qu'il y ait deux choses importantes à garder à l'esprit:

  1. Vous ne devez utiliser volatile que si vous comprendre complètement ce qu'il fait et comment il diffère de synchronisé. Dans de nombreuses situations volatile apparaît, sur la surface, pour être un plus simple alternative performante à synchronisé, quand souvent un meilleur la compréhension de volatile ferait clair que synchronisé est le seul option qui fonctionnerait.
  2. volatile ne fonctionne pas réellement dans un beaucoup de JVM plus anciennes, bien que synchronisé n'. Je me souviens avoir vu un document qui faisait référence aux différents niveaux de support dans différentes JVM mais malheureusement je ne le trouve pas maintenant. Regardez-y si vous utilisez Java pre 1.5 ou si vous n'avez pas le contrôle sur les JVM sur lesquelles votre programme s'exécutera.
 4
Author: MB., 2008-09-20 11:07:48

Absolument, oui. (Et pas seulement en Java, mais aussi en C#.) Il y a des moments où vous devez obtenir ou définir une valeur qui est garantie comme une opération atomique sur votre plate-forme donnée, un int ou booléen, par exemple, mais ne nécessite pas la surcharge du verrouillage des threads. Le mot-clé volatile vous permet de vous assurer que lorsque vous lisez la valeur, vous obtenez la valeur current et non une valeur mise en cache qui vient d'être rendue obsolète par une écriture sur un autre thread.

 3
Author: dgvid, 2012-01-06 14:28:48

Chaque thread accédant à un champ volatile lira sa valeur actuelle avant de continuer, au lieu (potentiellement) d'utiliser une valeur mise en cache.

Seule la variable membre peut être volatile ou transitoire.

 3
Author: tstuber, 2014-08-11 16:27:12

Il existe deux utilisations différentes du mot-clé volatile.

  1. Empêche la JVM de lire les valeurs du registre (supposons que le cache) et force sa valeur à être lue depuis la mémoire.
  2. Réduit le risque d'erreurs de cohérence de la mémoire.

Empêche la JVM de lire les valeurs dans le registre et force son valeur à lire de la mémoire.

Un drapeau occupé est utilisé pour empêcher un thread de continuer pendant que le périphérique est occupé et que le drapeau ne l'est pas protégé par une serrure:

while (busy) {
    /* do something else */
}

Le thread de test continuera lorsqu'un autre thread désactivera l'indicateur busy:

busy = 0;

Cependant, étant donné que busy est fréquemment consulté dans le thread de test, la JVM peut optimiser le test en plaçant la valeur de busy dans un registre, puis tester le contenu du registre sans lire la valeur de busy en mémoire avant chaque test. Le thread de test ne verrait jamais de changement occupé et l'autre thread ne changerait que la valeur de busy in mémoire, entraînant une impasse. Déclarer l'indicateur busy comme volatile force sa valeur à être lue avant chaque test.

Réduit le risque d'erreurs de cohérence de la mémoire.

L'utilisation de variables volatiles réduit le risque d'erreurs de cohérence mémoire , car toute écriture dans une variable volatile établit un "arrive-avant" relation avec les lectures ultérieures de cette même variable. Cela signifie que les modifications apportées à une variable volatile sont toujours visible pour les autres threads.

La technique de lecture, d'écriture sans erreurs de cohérence 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: soit elle arrive complètement, soit elle n'arrive pas du tout. Aucun effet secondaire d'une action atomique n'est visible jusqu'à la fin de l'action.

Voici les actions que vous pouvez spécifier qui sont atomiques:

  • Les lectures et les écritures sont atomique pour les variables de référence et pour la plupart variables primitives (tous types sauf long et double).
  • Lectures et écritures sont atomiques pour toutes les variables déclarées volatils (y compris les variables longues et doubles).

Santé!

 2
Author: dheeran, 2017-03-02 08:42:57

Les variables volatiles sont une synchronisation légère. Lorsque la visibilité des données les plus récentes parmi tous les threads est requise et que l'atomicité peut être compromise , dans de telles situations, des variables volatiles doivent être préférées. Lire sur les variables volatiles renvoie toujours l'écriture la plus récente effectuée par n'importe quel thread car elles ne sont ni mises en cache dans les registres ni dans les caches où les autres processeurs ne peuvent pas voir. Volatile est sans verrouillage. J'utilise volatile, lorsque le scénario répond aux critères mentionnés ci-dessus.

 2
Author: Neha Vari, 2018-02-06 12:57:06

De la documentation oracle page, le besoin de variable volatile se pose 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 mémoire, car toute écriture sur une variable volatile établit une relation happens-before avec les lectures ultérieures 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 un volatile variable, il voit non seulement la dernière modification apportée au volatile, mais aussi les effets secondaires du code qui a conduit au changement.

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 faisant de la variable volatile, les problèmes de cohérence de la mémoire ont été résolus.

Jetez un oeil à la page de tutorieljenkov pour une meilleure compréhension.

Jetez un oeil à la question SE connexe pour plus de détails sur volatile & cas d'utilisation pour utiliser volatile:

Différence entre volatile et synchronized en Java

Un cas d'utilisation pratique:

Vous avez de nombreux 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 pour chaque seconde. Tous les autres threads peuvent simplement utiliser cette variable volatile pour imprimer l'heure actuelle dans les fichiers journaux.

 1
Author: Ravindra babu, 2017-05-23 11:55:13

Une variable volatile est modifiée de manière asynchrone en exécutant simultanément des threads dans une application Java. Il n'est pas permis d'avoir une copie locale d'une variable différente de la valeur actuellement détenue dans la mémoire "principale". En effet, une variable déclarée volatile doit avoir ses données synchronisées sur tous les threads, de sorte que chaque fois que vous accédez ou mettez à jour la variable dans un thread, tous les autres threads voient immédiatement la même valeur. Bien sûr, il est probable que les variables volatiles avoir un accès et une mise à jour plus élevés que les variables "simples", car la raison pour laquelle les threads peuvent avoir leur propre copie de données est une meilleure efficacité.

Lorsqu'un champ est déclaré volatile, le compilateur et l'exécution sont avertis que cette variable est partagée et que les opérations sur elle ne doivent pas être réorganisées avec d'autres opérations de mémoire.Les variables volatiles ne sont pas mises en cache dans les registres ou dans les caches où elles sont cachées aux autres processeurs, donc une lecture d'un la variable volatile renvoie toujours l'écriture la plus récente par n'importe quel thread.

Pour référence, reportez-vous à ce http://techno-terminal.blogspot.in/2015/11/what-are-volatile-variables.html

 0
Author: satish, 2015-11-17 09:36:43

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 plusieurs threads lisent et écrivent dans une variable, rendre la variable volatile ne suffira pas et les données seront corrompues . Les threads d'image ont lu la même valeur mais chacun a fait quelques chages (disons incrémenté 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 rendre le variable synchronisé (différentes manières sont possibles)

Si les modifications sont effectuées par 1 thread et que les autres ont juste besoin de lire cette valeur, le volatile conviendra.

 0
Author: Java Main, 2015-12-24 16:41:21

Volatile fait ce qui suit.

1> La lecture et l'écriture de variables volatiles par différents threads proviennent toujours de la mémoire, pas du propre cache ou du registre cpu du thread. Ainsi, chaque thread traite toujours de la dernière valeur. 2 > Lorsque 2 threads différents fonctionnent avec la même instance ou des variables statiques dans le tas, on peut voir les actions des autres comme hors service. Voir le blog de jeremy manson à ce sujet. Mais volatile aide ici.

Le code en cours d'exécution complet montre comment un certain nombre de threads peuvent s'exécuter dans commandes prédéfinies et sorties d'impression 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 y parvenir, nous pouvons utiliser le code en cours d'exécution à part entière 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 readme, qui donne une explication appropriée. https://github.com/sankar4git/volatile_thread_ordering

 0
Author: sankar banerjee, 2018-05-31 06:23:43

J'aime l'explicationde jenkov

Le mot-clé Java volatile est utilisé pour marquer une variable Java comme "étant stockée dans la mémoire principale". Plus précisément, cela signifie que chaque lecture d'une variable volatile sera lue à partir de la mémoire principale de l'ordinateur, et non du cache CPU, et que chaque écriture dans une variable volatile sera écrite dans la mémoire principale, et pas seulement dans le cache CPU.

En fait, depuis Java 5, le mot-clé volatile garantit plus que cela variables sont écrites et lues depuis la mémoire principale. Il est garantie de visibilité étendue soi-disant arrive-avant garantie.

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 est plus coûteux que d'accéder au cache CPU. L'accès aux variables volatiles empêche également la réorganisation des instructions ce qui est une performance normale technique d'amélioration. Ainsi, vous ne devez utiliser des variables volatiles que lorsque vous avez vraiment besoin d'appliquer la visibilité des variables.

 0
Author: yoAlex5, 2018-06-16 14:16:55