Le concept de sans verrouillage a été mentionné dans l'introduction de [High Concurrence Java 1]. Puisqu'il existe un grand nombre d'applications sans verrouillage dans le code source JDK, Lock-Free est introduit ici.
1 Explication détaillée du principe de la classe sans verrouillage
1.1 CAS
Le processus de l'algorithme CAS est le suivant: il contient 3 paramètres CAS (V, E, N). V représente la variable à mettre à jour, E représente la valeur attendue et N représente la nouvelle valeur. Seulement si V
Lorsque la valeur est égale à la valeur E, la valeur de V sera définie sur N. Si la valeur V est différente de la valeur E, cela signifie que d'autres threads ont déjà fait des mises à jour et que le thread actuel ne fait rien. Enfin, CAS renvoie la véritable valeur des opérations actuelles de V. CAS est effectuée avec une attitude optimiste, et il croit toujours qu'il peut réussir les opérations. Lorsque plusieurs threads fonctionnent une variable en utilisant CAS en même temps, un seul gagnera et metra à jour avec succès, et le reste échouera. Le thread défaillant ne sera pas suspendu, il est seulement dit que l'échec est autorisé, et il est autorisé à réessayer, et bien sûr, le thread échoué permettra également d'abandonner l'opération. Sur la base de ce principe, CAS
L'opération est verrouillée immédiatement, et d'autres threads peuvent également détecter des interférences au thread actuel et le gérer de manière appropriée.
Nous constaterons qu'il y a trop d'étapes en CAS. Est-il possible qu'après avoir jugé que V et E soient les mêmes, lorsque nous sommes sur le point d'attribuer des valeurs, nous avons changé le thread et modifié la valeur. Qu'est-ce qui a provoqué une incohérence des données?
En fait, cette inquiétude est redondante. L'ensemble du processus de fonctionnement de CAS est une opération atomique, qui est complétée par une instruction CPU.
1.2 Instructions CPU
L'instruction CPU de CAS est CMMPXCHG
Le code d'instruction est le suivant:
/ * accumulateur = al, ax ou eax, selon que une comparaison octet, mot ou double mots est effectuée * / if (accumulateur == destination) {zf = 1; Destination = source; } else {zf = 0; accumulateur = destination; } Si la valeur cible est égale à la valeur dans le registre, un indicateur de saut est défini et les données d'origine sont définies sur la cible. Si vous n'attendez pas, vous ne définissez pas le drapeau de saut.
Java fournit de nombreuses classes sans verrouillage, alors introduisons des classes sans verrouillage ci-dessous.
2 inutile
Nous savons déjà que sans serrure est beaucoup plus efficace que le blocage. Jetons un coup d'œil à la façon dont Java implémente ces classes sans verrouillage.
2.1. Atomicinteger
AtomicInteger, comme Integer, hérite tous les deux de la classe de nombres
La classe publique AtomicInteger étend le nombre implémente Java.io.Serializable
Il existe de nombreuses opérations CAS dans ATOMICInteger, dont sont typiques:
Public Final Boolean ComparandDset (int attend, int mise à jour) {
Renvoie unpape.compareAndSwapint (ceci, ValueOffSet, attendez, mise à jour);
}
Ici, nous expliquerons la méthode dangereuse .PareAndSwapint. Cela signifie que si la valeur de la variable dont le décalage sur cette classe est ValueOffSet est la même que la valeur attendue, alors définissez la valeur de cette variable à mettre à jour.
En fait, la variable avec la valeur de décalage de décalage est la valeur
static {try {valuoffset = unsetafe.objectFieldOffSet (atomicInteger.class.getDeclaredField ("Value")); } catch (exception ex) {lance une nouvelle erreur (ex); }}Nous avons déjà dit que CAS peut échouer, mais le coût de l'échec est très faible, donc la mise en œuvre générale est en boucle infinie jusqu'à ce qu'elle réussit.
public final int getAndIncrement () {for (;;) {int courant = get (); int next = courant + 1; if (comparabledset (courant, suivant)) Retour courant; }}2.2 dangereux
À partir du nom de classe, nous pouvons voir que les opérations dangereuses sont des opérations non sécuritaires, telles que:
Définissez la valeur en fonction du décalage (j'ai vu cette fonction dans l'atomicInteger qui vient d'être introduit)
Park () (Arrêtez ce fil, il sera mentionné dans le futur blog)
L'opération CAS sous-jacente API non publique peut différer considérablement en différentes versions de JDK.
2.3. Atomique
AtomicInteger a été mentionné plus tôt, et bien sûr Atomicboolan, Atomiclong, etc. sont tous similaires.
Ce que nous voulons introduire ici, c'est l'atomique.
Atomicreference est une classe de modèle
Classe publique AtomiCreference <v> implémente Java.io.Serializable
Il peut être utilisé pour encapsuler tout type de données.
Par exemple, String
Test de package; import java.util.concurrent.atomic.atomicreference; Public Class Test {public final static atomiCreference <string> atomicstring = new atomiCreference <string> ("hosee"); public static void main (String [] args) {for (int i = 0; i <10; i ++) {final int num = i; nouveau thread () {public void run () {try {thread.sleep (math.abs ((int) math.random () * 100)); } catch (exception e) {e.printStackTrace (); } if (atomicstring.CompareAndSet ("Hosee", "ztk")) {System.out.println (Thread.currentThread (). GetId () + "Change Value"); } else {System.out.println (thread.currentThread (). getID () + "Faiched"); }}; }.commencer(); }}}résultat:
10fails
13fails
Valeur de 9 changements
11fails
12fails
15
17
14 ans
16
18 ans
Vous pouvez voir qu'un seul thread peut modifier la valeur et que les threads suivants ne peuvent plus le modifier.
2.4.AatomicstampeDreference
Nous constaterons qu'il y a encore un problème avec le fonctionnement CAS.
Par exemple, la méthode d'impréments et de gel précédente
public final int incmenmentAndget () {for (;;) {int current = get (); int next = courant + 1; if (comparableDset (courant, suivant)) renvoie ensuite; }} Supposons que la valeur actuelle = 1 lorsqu'un thread int current = get () s'exécute, passe à un autre thread, ce thread transforme 1 en 2, puis un autre thread transforme 2 en 1. À l'heure actuelle, passez au thread initial. Étant donné que la valeur est toujours égale à 1, les opérations CAS peuvent toujours être effectuées. Bien sûr, il n'y a aucun problème avec l'ajout. S'il y a certains cas, un tel processus ne sera pas autorisé lorsqu'il est sensible à l'état des données.
À l'heure actuelle, la classe ATOMICSTAMPEDRADREFERFE est requise.
Il implémente une classe de paire en interne pour encapsuler les valeurs et les horodatages.
Paire de classe statique privée <T> {référence finale T; Tampon INT final; paire privée (t référence, int tampon) {this.reference = référence; this.stamp = tampon; } statique <T> paire <T> de (t référence, int tampon) {return new pair <T> (référence, tampon); }}L'idée principale de cette classe est d'ajouter des horodatages pour identifier chaque changement.
// Comparez les paramètres des paramètres sont: la valeur attendue écrit la nouvelle valeur attendre l'horodatage nouvel horodatage
Public Boolean ComparendSet (v attendReference, v NewReference, int attendStamp, int NewStamp) {paire <v> current = paire; return attendReference == Current.Reference && attendStamp == current.stamp && ((newReference == current.reference && newstamp == current.stamp) || caspair (current, paire.of (newReference, newstamp)))); } Lorsque la valeur attendue est égale à la valeur actuelle et que l'obstruction attendue est égale à l'horodatage actuel, la nouvelle valeur est écrite et le nouvel horodatage est mis à jour.
Voici un scénario qui utilise ATOMICSTAMPEDREDERFEFER. Cela peut ne pas convenir, mais je ne peux pas imaginer un bon scénario.
L'arrière-plan de la scène est qu'une entreprise recharge les utilisateurs avec un faible équilibre gratuitement, mais chaque utilisateur ne peut se recharger qu'une seule fois.
Test de package; import java.util.concurrent.atomic.atomicstampeDeReference; Public Class Test {static atomicstampeDeReference <Integer> Money = new ATOMICSTAMPEDREFERFER <Integer> (19, 0); public static void main (String [] args) {for (int i = 0; i <3; i ++) {final int humstamp = monery.getStamp (); nouveau thread () {public void run () {while (true) {while (true) {Integer m = monery.getReference (); if (m <20) {if (money.compareAndset (m, m + 20, horodat, horodat + 1)) {System.out.println ("rechargez avec succès, équilibre:" + money.getReference ()); casser; }} else {Break; }}}}}}; }.commencer(); } new thread () {public void run () {for (int i = 0; i <100; i ++) {while (true) {int imberAmp = monery.getStamp (); Integer m = Money.GetReference (); if (m> 10) {if (money.compareAndset (m, m - 10, horodat, horodat + 1)) {System.out.println ("consommé 10 yuan, bilan:" + money.getReference ()); casser; }} else {Break; }} essayez {Thread.Sleep (100); } catch (exception e) {// todo: gère exception}}}}; }.commencer(); }}Expliquez le code, il y a 3 threads rechargez l'utilisateur. Lorsque l'équilibre de l'utilisateur est inférieur à 20, rechargez l'utilisateur 20 yuan. Il y a 100 threads consommant, chacun dépensant 10 yuans. L'utilisateur a initialement 9 yuans. Lorsque vous utilisez ATOMICSTAMPEDREDERFEFENCE pour l'implémenter, l'utilisateur ne sera rechargé qu'une seule fois, car chaque opération provoque l'horodatage +1. Résultats en cours:
Rechargez avec succès, équilibre: 39
Consommation de 10 yuans, équilibre: 29
Consommation de 10 yuans, équilibre: 19
Consommation de 10 yuans, équilibre: 9
Si vous utilisez ATomiCreference <Integer> ou entier atomique pour l'implémenter, il provoquera plusieurs recharges.
Rechargez avec succès, équilibre: 39
Consommation de 10 yuans, équilibre: 29
Consommation de 10 yuans, équilibre: 19
Rechargez avec succès, équilibre: 39
Consommation de 10 yuans, équilibre: 29
Consommation de 10 yuans, équilibre: 19
Rechargez avec succès, équilibre: 39
Consommation de 10 yuans, équilibre: 29
2.5. AtomicIntegerarray
Par rapport à AtomicInteger, la mise en œuvre des tableaux n'est qu'un indice supplémentaire.
Public Final Boolean ComparandDset (int i, int attend, int update) {
return ComparandDestraw (checkedByteOffset (i), attendez, mise à jour);
}
Son intérieur résume simplement un tableau normal
Arraie finale privée int [];
Ce qui est intéressant ici, c'est que les principaux zéros de nombres binaires sont utilisés pour calculer le décalage dans le tableau.
Shift = 31 - Integer.numberofLeadingzeros (échelle);
Le zéro leader signifie que par exemple, 8 bits représentent 12 00001100, alors le zéro principal est le nombre de 0 devant 1, soit 4.
Comment calculer le décalage n'est pas introduit ici.
2.6. Atomicintegerfieldupdater
La fonction principale de la classe AtomicIntegerFieldUpDater est de permettre aux variables ordinaires de profiter également des opérations atomiques.
Par exemple, il y avait à l'origine une variable qui était du type int, et cette variable a été appliquée à de nombreux endroits. Cependant, dans un certain scénario, si vous souhaitez transformer le type INT en atomicInteger, si vous modifiez directement le type, vous devez modifier l'application à d'autres endroits. AtomicIntegerFieldUpDater est conçu pour résoudre ces problèmes.
Test de package; import java.util.concurrent.atomic.atomicinteger; import java.util.concurrent.atomic.atomicIntegerFieldupDater; public class test {public static class v {int id; score INT volatile; public int getScore () {return score; } public void setScore (int score) {this.score = score; }} public final statique atomicintegerfieldupdater <v> vv = atomicIntegerFieldUpDater.newupDater (v.class, "score"); public static atomicInteger allScore = new atomicInteger (0); public static void main (String [] args) lève InterruptedException {final v stu = new v (); Thread [] t = nouveau thread [10000]; for (int i = 0; i <10000; i ++) {t [i] = new Thread () {@Override public void run () {if (math.random ()> 0.4) {vv.incrementAndget (stu); allScore.IncrementAndget (); }}}; t [i] .start (); } pour (int i = 0; i <10000; i ++) {t [i] .join (); } System.out.println ("score =" + stu.getScore ()); System.out.println ("allScore =" + allScore); }} Le code ci-dessus transforme le score à l'aide d'AtomicIntegerFieldUpDater en atomicInteger. Assurer la sécurité des filetages.
ALLSCORE est utilisé ici pour vérifier. Si le score et les valeurs ALLSCore sont les mêmes, cela signifie qu'il est en file d'attente.
Note: