La variable volatile offre une visibilité du fil et ne garantit pas la sécurité et l'atomicité du fil.
Qu'est-ce que la visibilité du fil:
Les verrous fournissent deux caractéristiques principales: l'exclusion mutuelle et la visibilité. L'exclusion mutuelle signifie qu'un seul thread peut maintenir un verrou spécifique à la fois, de sorte que cette fonctionnalité peut être utilisée pour implémenter un protocole d'accès coordonné pour les données partagées, afin qu'un seul thread puisse utiliser les données partagées à la fois. La visibilité est un peu plus complexe, et elle doit s'assurer que les modifications des données partagées avant de libérer le verrou sont visibles sur un autre thread qui acquiert par la suite la serrure - sans cette garantie de visibilité fournie par le mécanisme de synchronisation, les variables partagées observées par le thread peuvent être pré-modifiées ou des valeurs incohérentes, ce qui entraînera de nombreux problèmes graves.
Voir la sémantique de volatile :
Le volatile équivaut à une faible implémentation de synchronisée, ce qui signifie que le volatile implémente la sémantique synchronisée, mais n'a pas de mécanisme de verrouillage. Il garantit que les mises à jour du champ volatil informent les autres threads de manière prévisible.
Volatile contient la sémantique suivante:
(1) Le modèle de stockage Java ne réorganisera pas les opérations des instructions valatiles: Cela garantit que les opérations sur les variables volatiles sont exécutées dans l'ordre dans lequel les instructions apparaissent.
(2) La variable volatile ne sera pas mise en cache dans le registre (seuls les threads sont visibles) ou d'autres endroits qui ne sont pas visibles pour le CPU. Le résultat de la variable volatile est toujours lu à partir de la mémoire principale à chaque fois. En d'autres termes, pour la modification de la variable volatile, les autres threads sont toujours visibles et n'utilisent pas de variables à l'intérieur de la pile de threads. Autrement dit, dans la loi sur les passages avant, après avoir écrit une variable valatile, toute opération de lecture ultérieure peut être comprise pour voir le résultat de cette opération d'écriture.
Bien que les caractéristiques de la variable volatile soient bonnes, la volatile ne peut garantir la sécurité des filetages. C'est-à-dire que le fonctionnement du champ volatil n'est pas atomique. La variable volatile ne peut garantir que la visibilité (d'autres threads peuvent comprendre les résultats après avoir vu ce changement après la modification d'un thread). Pour assurer l'atomicité, vous ne pouvez le verrouiller que jusqu'à présent!
Principes pour l'utilisation du volatile:
Trois principes pour appliquer des variables volatiles:
(1) Les variables d'écriture ne dépendent pas de la valeur de cette variable, ou un seul thread modifie cette variable
(2) L'état de la variable n'a pas besoin de participer aux contraintes invariantes avec d'autres variables
(3) Les variables d'accès n'ont pas besoin d'être verrouillées
En fait, ces conditions indiquent que ces valeurs valides qui peuvent être écrites à la variable volatile sont indépendantes de l'état de tout programme, y compris l'état actuel de la variable.
Les limites des premières conditions empêchent la variable volatile d'être utilisée comme compteur en filetage. Bien que l'opération incrémentielle (X ++) ressemble à une opération distincte, il s'agit en fait d'une opération combinée composée d'une séquence d'opérations de lecture-modification-écriture qui doivent être effectuées atomiquement, et volatile ne peut pas fournir les propriétés atomiques nécessaires. La mise en œuvre de l'opération correcte nécessite de garder la valeur constante de X pendant l'opération, ce qui n'est pas possible avec la variable volatile. (Cependant, si la valeur est ajustée pour être écrite uniquement à partir d'un seul thread, la première condition peut être ignorée.)
La plupart des situations de programmation entrent en conflit avec l'une de ces trois conditions, ce qui rend la variable volatile non aussi universellement applicable à la sécurité des filetages comme synchronisée. Listing 1 montre une classe de plage numérique non sécurisée sans fil. Il contient un invariant - la limite inférieure est toujours inférieure ou égale à la borne supérieure.
Utilisez correctement volatile:
Mode n ° 1: indicateurs d'état
Peut-être que la spécification de la mise en œuvre de la variable volatile est simplement d'utiliser un indicateur de statut booléen pour indiquer qu'un événement unique important s'est produit, tel que la réalisation de l'initialisation ou la demande de temps d'arrêt.
De nombreuses applications incluent une structure de contrôle sous la forme de "exécuter des travaux lorsque le programme n'est pas prêt à s'arrêter", comme indiqué dans Listing 2:
Listing 2. Utilisez la variable volatile comme indicateur d'état
Boolean volatile Shutdown a été questionné; … Public void shutdown () {shutdownRequest = true; } public void Dowork () {while (! shutdownRequeted) {// faire des trucs}}Il est très probable que la méthode d'arrêt () sera appelée depuis l'extérieur de la boucle - c'est-à-dire dans un autre thread - donc une sorte de synchronisation doit être effectuée pour garantir que la visibilité de la variable de fermeture est correctement mise en œuvre. (Peut être appelé de l'écouteur JMX, écouteur d'opération dans le fil d'événement GUI, via RMI, via un service Web, etc.). Cependant, l'écriture d'une boucle avec un bloc synchronisé est beaucoup plus gênante que d'écrire avec l'indicateur d'état volatil indiqué dans la liste 2. Parce que volatile simplifie le codage et les drapeaux d'état ne dépendent d'aucun autre état du programme, il est très adapté à la volatile ici.
Une caractéristique commune de ce type de balise d'état est qu'il n'y a généralement qu'une seule transition d'état; Le drapeau de fermeture est converti de False en vrai, et le programme s'arrête. Ce modèle peut être étendu au drapeau d'état de la transition de va-et-vient, mais ne peut être étendu que si la période de transition n'est pas remarquée (de faux à vrai, alors à false). De plus, certains mécanismes de conversion de l'état atomique sont nécessaires, tels que les variables atomiques.
Mode n ° 2: Publication unique
Le manque de synchronisation peut conduire à une visibilité irréalisable, ce qui rend plus difficile de déterminer quand écrire une référence d'objet au lieu d'une valeur primitive. En l'absence de synchronisation, une valeur mise à jour référencée par un objet (écrit par un autre thread) peut être rencontrée et l'ancienne valeur de l'état de cet objet existe simultanément. (C'est la cause profonde du célèbre problème de verrouillage à double vérification où les références d'objets sont lues sans synchronisation, ce qui a entraîné le problème que vous pouvez voir une référence mise à jour, mais voyez toujours un objet incomplètement construit par cette référence).
Une technique pour mettre en œuvre une publication sûre d'objets consiste à définir les références d'objets comme un type volatile. Listing 3 montre un exemple où le thread d'arrière-plan charge certaines données de la base de données pendant le démarrage. D'autres codes, lorsqu'ils sont en mesure d'utiliser ces données, vérifiez s'il a été publié avant utilisation.
Listing 3. Utilisation de la variable volatile pour une version unique
classe publique BackgroundFlooBbleLoader {public volatile floble theflooble; public void inIntinBackground () {// faire beaucoup de choses thefloble = new Flooble (); // Ceci est la seule écriture à thefloble}} classe publique Someotherclass {public void Dowork () {while (true) {// faire des trucs… // utilisez le flooble, mais seulement si elle est prête si (floobleloder.theflooble); }}}Si la référence flooble n'est pas un type volatil, le code dans Dowork () obtiendra un logement incomplètement construit lorsqu'il vous référera.
Une condition nécessaire à ce modèle est que l'objet publié doit être une file d'attente, ou un objet immuable valide (un immuable efficace signifie que l'état de l'objet ne sera jamais modifié après la publication). Les références de type volatile garantissent la visibilité du formulaire de publication de l'objet, mais une synchronisation supplémentaire est requise si l'état de l'objet change après publication.
Modèle n ° 3: observation indépendante
Un autre mode simple d'utilisation du volatil en toute sécurité consiste à "libérer" des observations régulièrement pour l'utilisation interne du programme. Par exemple, supposons qu'il existe un capteur ambiant capable de détecter la température ambiante. Un thread d'arrière-plan peut lire le capteur toutes les quelques secondes et mettre à jour la variable volatile contenant le document actuel. Ensuite, d'autres threads peuvent lire cette variable afin qu'ils puissent voir la dernière valeur de température à tout moment.
Une autre application qui utilise ce mode consiste à collecter les statistiques du programme. Listing 4 montre comment le mécanisme d'authentification se souvient du nom de l'utilisateur qui a été connecté pour la dernière fois. Répétez la référence LastUser pour publier des valeurs pour d'autres parties du programme.
Listing 4. Utilisation de la variable volatile pour publier plusieurs observations indépendantes
classe publique Usermanager {public volatile String LastUser; public boolean authenticiate (String User, String Motword) {boolean valid = mot de passe envalid (utilisateur, mot de passe); if (valide) {utilisateur u = new user (); activeUsers.add (u); LastUser = utilisateur; } retour valide; }}Ce mode est une extension du mode précédent; Publier une certaine valeur d'utilisation ailleurs dans le programme, mais contrairement à la publication d'un événement unique, il s'agit d'une série d'événements indépendants. Ce modèle nécessite que la valeur publiée soit valide et immuable - c'est-à-dire que le statut de la valeur ne changera pas après la publication. Le code qui utilise cette valeur doit être clair que la valeur peut changer à tout moment.
Mode n ° 4: mode "haricot volatil"
Le motif de haricots volatils convient aux cadres qui utilisent les javabeans comme une "structure d'honneur". Dans le motif de haricots volatils, les javabeans sont utilisés comme un ensemble de conteneurs aux propriétés indépendantes des méthodes Getter et / ou Setter. Le principe de base du motif de haricots volatils est que de nombreux cadres fournissent des conteneurs pour les détenteurs de données volatiles (telles que HTTPSession), mais les objets placés dans ces conteneurs doivent être en file d'attente.
En mode haricot volatil, tous les membres de données d'un Javabean sont de type volatil, et les méthodes Getter et Setter doivent être très ordinaires - elles ne peuvent contenir aucune logique sauf pour obtenir ou définir des propriétés correspondantes. De plus, pour les membres de données référencés par l'objet, l'objet référencé doit être valide et immuable. (Cela interdit les propriétés avec les valeurs du tableau, car lorsqu'une référence de tableau est déclarée volatile, seule la référence et non le tableau lui-même a une sémantique volatile). Pour toute variable volatile, les invariants ou les contraintes ne peuvent pas contenir des propriétés Javabean. Les exemples de Listing 5 montrent des javabeans qui adhèrent au motif de haricots volatils:
Mode n ° 4: mode "haricot volatil"
@Threadsafe Public Class Person {private volatile String FirstName; String volatile privé LastName; Int Âge privé volatile; public String getFirstName () {return firstName; } public String getLastName () {return lastName; } public int getage () {return âge; } public void setFirstName (String FirstName) {this.firstName = FirstName; } public void setLastName (String LastName) {this.lastName = LastName; } public void Setage (int Age) {this.age = age; }}Mode avancé de volatile
Les modèles décrits dans les sections précédents couvrent la plupart des cas d'utilisation de base, et l'utilisation de volatils dans ces modèles est très utile et simple. Cette section introduit un mode plus avancé dans lequel volatile offrira des avantages de performances ou d'évolutivité.
Les modes avancés d'applications volatiles sont très fragiles. Par conséquent, les hypothèses doivent être soigneusement prouvées, et ces modèles sont strictement encapsulés car même de très petits changements peuvent corrompre votre code! De même, la raison de l'utilisation d'un cas d'utilisation volatile plus avancé est qu'il peut améliorer les performances, en vous assurant que vous déterminez vraiment que vous devez obtenir cet avantage de performance avant de commencer à appliquer les modèles avancés. Il y a des compromis sur ces modèles, abandonnant la lisibilité ou la maintenabilité en échange de gains de performances possibles - si vous n'avez pas besoin d'améliorations de performances (ou que vous ne pouvez pas prouver que vous en avez besoin grâce à un programme de test strict), alors ce sera probablement une mauvaise affaire car vous perdrez probablement moins d'argent et obtiendrez quelque chose de moins que ce que vous abandonnez.
Mode n ° 5: stratégie de verrouillage en lecture avec des frais généraux faibles
Jusqu'à présent, vous devez comprendre que Volatile n'est pas suffisamment capable pour implémenter des compteurs. Parce que ++ x est en fait une combinaison simple de trois opérations (lire, ajouter, stocker), si plusieurs threads tentent d'essayer d'effectuer des opérations incrémentielles sur le compteur volatil en même temps, sa valeur mise à jour peut être perdue.
Cependant, si l'opération de lecture est bien plus que l'opération d'écriture, vous pouvez utiliser un verrouillage interne et des variables volatiles pour réduire les frais généraux du chemin du code public. Les compteurs de filetage illustrés dans Listing 6 utilisent synchronisé pour garantir que les opérations incrémentielles sont atomiques et volatiles pour garantir la visibilité du résultat actuel. Cette méthode peut atteindre de meilleures performances si les mises à jour ne sont pas fréquentes, car la surcharge des chemins de lecture implique uniquement l'opération de lecture volatile, qui est généralement meilleure que la surcharge d'une acquisition de verrouillage sans concours.
Listing 6. Utilisez un volatil et synchronisé pour réaliser "des verrous en lecture aérienne plus bas" "
@Threadsafe public class CheesyCounter {// utilise l'astuce de verrouillage de lecture à lecture bon marché // Toutes les opérations mutatives doivent être effectuées avec la valeur int privée «ce» verrouillage @guardedBy («this») privé volatile int; public int getValue () {return Value; } public synchronisé Int incrément () {return Value ++; }}La raison pour laquelle cette technique est appelée "verrouillage de lecture au-dessus de la tête" est dû au fait que vous utilisez un mécanisme de synchronisation différent pour les opérations en lecture-écriture. Étant donné que l'opération d'écriture dans cet exemple viole la première condition d'utilisation volatile, le compteur ne peut pas être implémenté en toute sécurité avec un volatile - vous devez utiliser une serrure. Cependant, vous pouvez utiliser des opérations de lecture volatiles pour assurer la visibilité de la valeur actuelle, afin que vous puissiez utiliser des verrous pour effectuer toutes les modifications et en lecture seule avec volatile. Parmi eux, le verrou permet uniquement un seul thread pour accéder à la valeur à la fois, et Volatile permet à plusieurs threads d'effectuer des opérations de lecture. Par conséquent, lors de l'utilisation du volatile pour s'assurer que le chemin de code est lu, il est plus partagé que d'utiliser le verrou pour exécuter tous les chemins de code - tout comme une opération en lecture-écriture. Cependant, gardez à l'esprit les faiblesses de ce modèle à l'esprit: si l'application la plus élémentaire de ce modèle est au-delà, il deviendra très difficile de combiner ces deux mécanismes de synchronisation concurrents.
À propos de la réorganisation de l'instruction et de la règle des passages avant
1. Laissez la réorganisation
La spécification du langage Java stipule que le thread JVM maintient la sémantique séquentielle en interne, c'est-à-dire que le résultat final du programme équivaut à ses résultats dans un environnement séquentiel strict, l'ordre d'exécution des instructions peut être incompatible avec l'ordre du code. Ce processus est réorganisé par une commande. L'importance de la réorganisation des instructions est que le JVM peut réorganiser de manière appropriée les instructions de la machine en fonction des caractéristiques du processeur (système de cache à plusieurs niveaux du CPU, processeur multi-core, etc.), afin que les instructions de la machine soient plus conformes aux caractéristiques d'exécution du CPU et à maximiser les performances de la machine.
Le modèle le plus simple pour l'exécution du programme consiste à exécuter dans l'ordre dans lequel les instructions apparaissent, qui est indépendante du CPU qui exécute les instructions, assurant la portabilité des instructions dans la plus grande mesure. Le terme professionnel pour ce modèle est appelé modèle de cohérence séquentielle. Cependant, les systèmes informatiques modernes et les architectures de processeur ne garantissent pas cela (car la désignation artificielle ne peut pas toujours garantir la conformité aux caractéristiques de traitement du processeur).
2. Règle des appels avant
Le modèle de stockage Java a un principe en passant avant, c'est-à-dire que si l'action B veut voir le résultat de l'exécution de l'action A (peu importe si A / B est exécuté dans le même thread), alors A / B doit satisfaire à la relation en passant avant.
Avant d'introduire la règle sur les passages avant, introduisez une action concept: JMM (Java Memeory Model Action), Java Stores Model Actions. Une action comprend: les variables de lecture et d'écriture, les verrous de verrouillage et de libération de moniteur, le thread start () et le join (). La serrure sera mentionnée plus tard.
Les règles complètes sont avantageuses:
(1) Chaque action dans le même thread se produit avant toute action qui apparaît après.
(2) Le déverrouillage d'un moniteur se produit avant chaque verrouillage suivant sur le même moniteur.
(3) Écrivez l'opération sur le champ volatil qui se passe avant chaque opération de lecture ultérieure du même champ.
(4) L'appel à thread.start () se déroulera avant les actions du thread de démarrage.
(5) Toutes les actions dans le thread se déroulent avant les autres threads pour terminer ce thread ou retourner dans thread.join () ou thread.isalive () == false.
(6) Un thread A appelle l'interruption () d'un autre thread B se produit avant lorsque le thread A constate que B est interrompu par A (B lance une exception ou un détecte B est Interrupted () ou interrompu ()).
(7) La fin d'un constructeur d'objet se déroule avant et le début du finalisateur de l'objet
(8) Si une action se produit dans l'action B, et que l'action B est avant et C de l'action, alors une action se produit dans l'action C.
Ce qui précède concerne cet article. Je vous le présenterai ici. J'espère qu'il vous sera utile d'apprendre et de comprendre les variables volatiles en Java.