Avant de lire cet article, vous pouvez d'abord lire " Introduction et utilisation du package atomique multithread Java " pour en savoir plus sur le contenu pertinent du package atomique.
1. Qu'est-ce que l'atomique?
Le mot atomique a quelque chose à voir avec les atomes, qui était autrefois considéré comme l'unité de la plus petite affaire. L'atomique dans un ordinateur signifie qu'il ne peut pas être divisé en plusieurs parties. Si un morceau de code est considéré comme atomique, cela signifie que le code ne peut pas être interrompu pendant l'exécution. De manière générale, les instructions atomiques sont fournies par le matériel et sont fournies par des logiciels pour implémenter des méthodes atomiques (un thread ne sera pas interrompu après la saisie de la méthode jusqu'à la fin de son exécution)
Sur la plate-forme x86, le CPU fournit les moyens de verrouiller le bus pendant l'exécution de l'instruction. Il y a un lead #hlockpin sur la puce CPU. Si le "verrouillage" du préfixe est ajouté à une instruction dans le programme de langage d'assemblage, le code de la machine d'assemblage entraînera la baisse du CPU du potentiel de #HlockPin lors de l'exécution de cette instruction et la libérer jusqu'à la fin de cette instruction, verrouillant ainsi le bus. De cette façon, d'autres processeurs sur le même bus ne peuvent pas accéder à la mémoire via le bus pour le moment, assurant l'atomicité de cette instruction dans un environnement multiprocesseur.
2. Variables atomiques dans java.util.concurrent
Qu'ils soient directs ou indirects, presque toutes les classes du package java.util.concurrent utilisent des variables atomiques, et non la synchronisation. Des classes comme ConcurrentLinkEdQueue utilisent également des variables atomiques pour implémenter directement l'algorithme sans attente, tandis que des classes comme ConcurrentHashMap utilisent ReentrantLock pour verrouiller en cas de besoin. Reentrantlock utilise ensuite des variables atomiques pour maintenir la file d'attente en attente de verrouillage.
Sans améliorations JVM dans JDK5.0, ces classes ne seront pas construites, qui exposeront (à la bibliothèque de classe, pas à la classe d'utilisateurs) pour accéder aux primitives de synchronisation au niveau matériel. Les classes de variables atomiques et autres classes dans java.util.concurrent exposent ensuite ces fonctions à la classe utilisateur
classe atomique de java.util.concurrent.atomic
Ce package fournit un ensemble de classes atomiques. Sa fonction de base est que dans un environnement multithread, lorsque plusieurs threads exécutent des méthodes contenues dans les cas de ces classes en même temps, elle est exclusive, c'est-à-dire lorsqu'un thread entre la méthode et exécute les instructions, il ne sera pas interrompu par d'autres threads et que d'autres threads sont comme des verrous de spin. Jusqu'à ce que la méthode soit exécutée, le JVM sélectionnera un autre thread dans la file d'attente d'attente pour entrer. Ce n'est qu'une compréhension logique. En fait, il est implémenté à l'aide d'instructions matérielles pertinentes et ne bloquera pas les threads (ou il est simplement bloqué au niveau matériel). Les classes peuvent être divisées en 4 groupes
Atomicboolean, atomicinteger, atomiclong, atomicreference
AtomicIntegerarray, atomiclongarray
AtomiclongfieldupDater, atomicintegerfieldupdater, atomicreferencefieldupdater
AtomicmarkableReference, atomicstampeDeDeference, atomicreferencearray
Parmi eux, l'atomicboolen, l'atomicinteger, l'atomiclong, l'atomicreference sont similaires.
Tout d'abord, atomicboolean, atomicinteger, atomiclong, atomicreference Les API internes sont similaires: prenez un exemple d'atomicure
Créez une pile de filetage avec atomicure
classe publique LinkedStack <T> {private atomicreference <node <T>> Stacks = new atomiCreference <node <T>> (); public t push (t e) {node <t> oldNode, newNode; tandis que (true) {// le traitement ici est très spécial, et ce doit être le cas. OldNode = stacks.get (); newNode = new node <t> (e, oldNode); if (stacks.compareAndset (oldNode, newNode)) {return e;}}} public t pop () {node <t> oldNode = oldnode (Stacks.CompareAndSet (OldNode, newNode)) {return OldNode.Object;}}} Node de classe finale statique privée <T> {Private T Object; Node privé <T> Suivant; Node privé (T objet, nœud <T> Suivant) {this.object = objet; this.next = Next;}}}}Concentrez-vous ensuite sur la mise à jour atomique du champ.
AtomicIntegerFieldUpDater <T> / ATOMICLONGFIELDUPDATER <T> / ATOMICREFENDEFFIELDUPDATER <T, V> est la valeur d'un champ basé sur la réflexion.
L'API correspondante est également très simple, mais elle a également quelques contraintes.
(1) Le champ doit être de type volatile! Qu'est-ce que volatile? Veuillez vérifier " Explication détaillée des mots clés volatils en Java "
(2) Le type de description du champ (modificateur public / protégé / par défaut / privé) est cohérent avec la relation entre l'appelant et le champ d'objet d'opération. C'est-à-dire que l'appelant peut exploiter directement le champ d'objet, puis refléter et effectuer des opérations atomiques. Cependant, pour les champs de la classe parent, la sous-classe ne peut pas être utilisée directement, bien que la sous-classe puisse accéder aux champs de la classe parent.
(3) Il ne peut être qu'une variable d'instance, pas une variable de classe, c'est-à-dire qu'elle ne peut pas ajouter de mots clés statiques.
(4) Il ne peut être modifié que des variables, et il ne peut pas être transformé en variables finales, car la sémantique de la finale n'est pas modifiée. En fait, la sémantique des conflits finaux et volatils, et ces deux mots clés ne peuvent exister en même temps.
(5) Pour atomicIntegerFieldUpDater et AtomiclongFieldUpDater, ils ne peuvent modifier que des champs de type int / long et ne peuvent pas modifier leur type de wrapper (entier / long). Si vous souhaitez modifier le type d'emballage, vous devez utiliser AtomiCreferenceFieldUpDater.
La méthode de fonctionnement est décrite dans l'exemple suivant.
import java.util.concurrent.atomic.atomicintegerFieldUpDater; classe publique atomicintegerFieldUpDaterdemo {class Demodata {public volatile int value1 = 1; volatile int value2 = 2; protégée volatile int value3 = 3; private volatile intwalue4 = 4;} atomicintrefreafrea FieldName) {return atomicIntegerFieldUpDater.newupDater (Demodata.class, fieldname);} void DOIT () {Demodata Data = new Demodata (); System.out.println ("1 ==>" + geutUpDater ("Value1"). "+ getUpDater (" value2 "). incrémentAndget (data)); System.out.println (" 2 ==> "+ getUpDater (" Value3 "). DecmenmentAndget (data)); System.out.println (" true ==> "+ getUpDater (" Value4 "). Comparg {AtomicIntegerFieldUpDaterDemo Demo = new AtomicIntegerFieldUpDaterDemo (); Demo.doiit ();}}Dans l'exemple ci-dessus, le champ Value3 / Value4 de Demodata n'est pas visible par la classe AtomicIntegerFieldUpDaterDemo, donc sa valeur ne peut pas être directement modifiée par réflexion.
Une paire d'objets <objet, booléen> décrits par la classe atomicmarkablereference peut modifier atomiquement la valeur de l'objet ou du booléen. Cette structure de données est plus utile dans certaines descriptions de cache ou d'état. Cette structure peut efficacement améliorer le débit lors de la modification de l'objet / booléen individuellement ou simultanément.
La classe ATOMICSTAMPEDRADERERFET maintient les références d'objets avec des "drapeaux" entiers et peut être mis à jour atomiquement. Par rapport à l'objet, Boolean> de la classe ATOMICMATIBLABLEREFENTE, ATOMICSTAMPEDREDREFENTE maintient une structure de données similaire à <objet, int>, qui est en fait un nombre simultané d'objets (références). Mais contrairement à AtomicInteger, cette structure de données peut porter une référence d'objet et peut effectuer des opérations atomiques sur cet objet et compter en même temps.
"ABA Problème" sera mentionné à la fin de cet article, et ATOMICMARKABLEREFEREFER / ATOMICSTAMPEDREDREFENDE est utile pour résoudre "ABA Problem".
Iii. Le rôle de l'atomique
Cela permet à l'opération de données uniques d'être atomisées
Créer un code complexe et sans blocage à l'aide de classes atomiques
L'accès à 2 variables atomiques ou plus (ou effectuant 2 opérations ou plus sur une seule variable atomique) est généralement considérée comme nécessitant une synchronisation afin que ces opérations puissent être utilisées comme unité atomique.
Pas de verrouillage et d'algorithme d'attente
Les algorithmes de concurrence basés sur le CAS (comparaison deswap) sont appelés algorithmes sans verrouillage car les threads n'ont plus à attendre le verrouillage (parfois appelé mutex ou parties critiques, selon la terminologie de la plate-forme de thread). Que l'opération CAS réussit ou échoue, dans les deux cas, elle est terminée dans un délai prévisible. Si CAS échoue, l'appelant peut réessayer l'opération CAS ou prendre d'autres opérations appropriées.
Si chaque thread continue de fonctionner lorsque d'autres threads sont retardés (ou même échoués), on peut dire que l'algorithme est libre. En revanche, l'algorithme sans verrouillage nécessite que seul un certain fil effectue toujours l'opération. (Une autre définition de No Wait consiste à s'assurer que chaque thread calcule correctement ses propres opérations à ses étapes limitées, quelles que soient les opérations, le calendrier, le croisement ou la vitesse d'autres threads. Cette limite peut être une fonction du nombre de threads dans le système; par exemple, s'il y a 10 threads, chaque thread effectue une opération Cascounter.
Au cours des 15 dernières années, les gens ont mené des recherches approfondies sur des algorithmes sans attente et sans verrouillage (également appelés algorithmes non bloquants), et de nombreuses personnes ont découvert des algorithmes non bloquants dans les structures de données générales. Les algorithmes non bloquants sont largement utilisés au niveau du système d'exploitation et de JVM, effectuant des tâches telles que le filetage et la planification de processus. Bien que leur mise en œuvre soit plus complexe, ils présentent de nombreux avantages par rapport aux algorithmes alternatifs basés sur les verrouillage: ils peuvent éviter les dangers tels que l'inversion prioritaire et la blocage, la concurrence est moins chère, la coordination se produit à un niveau de granularité plus fin, permettant un degré de parallélisme plus élevé, etc.
Commun:
Compteur non bloquant
Pile non bloquante concurrentStack
Liste non liée non bloquante concurrentLinkEdQueue
Questions ABA:
Parce qu'avant de changer V, le CAS demande principalement "si la valeur de V est toujours un", avant de lire V pour la première fois et d'effectuer des opérations CAS sur V, en modifiant la valeur de A à B, puis en retournant à A confondera l'algorithme basé sur CAS. Dans ce cas, l'opération CAS réussira, mais dans certains cas, le résultat n'est peut-être pas ce que vous attendez. Ce type de problème est appelé problème ABA, qui est généralement géré en associant une balise ou un numéro de version à chaque valeur à effectuer sur l'opération CAS et à mettre à jour atomiquement les valeurs et les balises. La classe ATOMICSTAMPEDREDREFERFE prend en charge cette méthode.
Résumer
Ce qui précède est toutes les explications détaillées de cet article sur le fonctionnement des variables atomiques et des classes atomiques dans le paquet atomique multithread Java. J'espère que ce sera utile à tout le monde. Les amis intéressés peuvent continuer à se référer à ce site:
Programmation Java: l'impasse multi-thread et la communication entre les threads sont un code simple
Java Programmation multi-thread
Une brève discussion sur les avantages et les exemples de code de Java Multithreading
S'il y a des lacunes, veuillez laisser un message pour le signaler.