Qu'est-ce qu'un verrou de spin
En parlant de verrous de spin, nous devons commencer par le mécanisme de verrouillage sous le multi-threading. Étant donné que certaines ressources dans un environnement système multiprocesseur sont limitées, elles nécessitent parfois une exclusion mutuelle. À l'heure actuelle, un mécanisme de verrouillage sera introduit. Seul le processus qui acquiert le verrou peut obtenir un accès aux ressources. Autrement dit, un seul processus peut acquérir la serrure à la fois pour entrer dans sa propre zone critique. Dans le même temps, deux processus ou plus ne peuvent pas entrer dans la zone critique. Lors de la sortie de la zone critique, la serrure sera libérée.
Lorsque vous concevez un algorithme Mutex, vous êtes toujours confronté à une situation où vous n'avez pas de verrou, c'est-à-dire que devez-vous faire si vous n'obtenez pas de serrure?
Il y a généralement 2 façons de s'occuper de cela:
La première est que l'appelant qui n'a pas obtenu le verrou est en boucle pour voir si le titulaire du verrou de spin a libéré le verrou. C'est l'objectif de cet article - Spin Lock. Il n'a pas à bloquer la ville de Line (non bloquante).
Une autre façon est que le processus sans obtenir le verrouillage se bloque (blocage) et continue d'exécuter d'autres tâches sur le fil, qui est le mutex (y compris le verrou intégré synchronisé, reentrantlock, etc.).
introduction
CAS (comparer et échanger), c'est-à-dire la comparaison et l'échange, est également l'opération principale qui implémente ce que nous appelons généralement le verrouillage de spin ou le verrou optimiste.
Son implémentation est très simple, qui consiste à comparer une valeur attendue avec une valeur de mémoire. Si les deux valeurs sont égales, remplacez la valeur de mémoire par la valeur attendue et renvoyez True. Sinon, retournez false.
Assurer le fonctionnement atomique
Toute technologie émerge pour résoudre certains problèmes spécifiques. Le problème que CAS doit résoudre est d'assurer les opérations atomiques. Qu'est-ce qu'une opération atomique? Les atomes sont les plus petits et les plus indécents, et l'opération atomique est l'opération la plus petite et la plus indécente. C'est-à-dire que, une fois l'opération démontr, elle ne peut pas être interrompue et sait que l'opération est terminée. Dans un environnement multi-thread, les opérations atomiques sont un moyen important d'assurer la sécurité des filetages. Par exemple, supposons qu'il y ait deux threads qui fonctionnent et souhaitent modifier une certaine valeur. Prenez l'opération d'auto-incidence comme exemple. Pour effectuer une opération d'auto-incidence sur un entier I, trois étapes de base sont nécessaires:
1. Lisez la valeur actuelle de i;
2. Ajouter 1 à la valeur i;
3. Écrivez la valeur i en mémoire;
Supposons que les deux processus lisent la valeur actuelle de I, en supposant qu'il est 0, pour le moment, le thread A ajoute 1 à I, le thread B ajoute également 1, et finalement i est 1, pas 2. En effet Comme dans l'exemple ci-dessous, pour 10 threads, chaque fil effectue 10 000 opérations I ++, la valeur attendue est de 100 000, mais malheureusement, le résultat est toujours inférieur à 100 000.
statique int i = 0; public static void add () {i ++; } classe statique privée Plus implémente Runnable {@Override public void run () {for (int k = 0; k <10000; k ++) {add (); }}} public static void main (String [] args) lève InterruptedException {thread [] threads = new Thread [10]; for (int i = 0; i <10; i ++) {threads [i] = new thread (new plus ()); Threads [i] .start (); } pour (int i = 0; i <10; i ++) {threads [i] .join (); } System.out.println (i); }Dans ce cas, que dois-je faire? C'est vrai, peut-être que vous y avez déjà pensé, vous pouvez verrouiller ou utiliser l'implémentation synchronisée, par exemple, modifier la méthode Add () à ce qui suit:
public synchronisé statique void add () {i ++; }Alternativement, l'opération de verrouillage est implémentée, par exemple, en utilisant ReentrantLock (ReentrantLock).
Lock de verrouillage statique privé = new reentrantLock (); public static void add () {lock.lock (); i ++; lock.unlock (); } CAS implémente le verrouillage de spin
Étant donné que les opérations atomiques peuvent être implémentées à l'aide du verrou ou du mot clé synchronisé, pourquoi utiliser CAS? Parce que le verrouillage ou l'utilisation de mots clés synchronisés entraîne une grande perte de performances, tandis que l'utilisation de CAS peut obtenir un verrouillage optimiste. Il utilise en fait directement les instructions au niveau du processeur, donc les performances sont très élevées.
Comme mentionné ci-dessus, CAS est la base de la mise en œuvre des verrous de spin. CAS utilise les instructions du processeur pour assurer l'atomicité de l'opération pour réaliser l'effet de verrouillage. Quant au spin, il est également très clair de lire le sens littéral. Si vous le tournez vous-même, c'est une boucle. Il est généralement implémenté à l'aide d'une boucle infinie. De cette façon, une opération CAS est exécutée dans une boucle infinie. Lorsque l'opération est réussie et revient vrai, la boucle se termine; Lorsqu'il est faux, la boucle est exécutée et l'opération CAS se poursuit jusqu'à ce que le vrai soit retourné.
En fait, de nombreux endroits dans le JDK utilisent CAS, en particulier dans le package java.util.concurrent, tels que CountdownLatch, Semaphore, ReentrantLock et Java.util.Concurrent.atomic. Je crois que tout le monde a utilisé l'atomique *, comme Atomicboolan, AtomicInteger, etc.
Ici, nous prenons atomicboolean comme exemple, car c'est assez simple.
classe publique Atomicboolean implémente Java.io.Serializable {private statique final long SerialVersionUID = 4654671469794556979l; // Configuration pour utiliser unpape.compareAndSwapint pour les mises à jour finale statique privée dangereuse dangetafe = unsetafe.getUnSafe (); Finale statique privée Long ValueOffset; static {try {valuoffset = unsetafe.ObjectFieldOffSet (atomicboolean.class.getDeclaredField ("Value")); } catch (exception ex) {lance une nouvelle erreur (ex); }} valeur int privée volatile; public final boolean get () {return value! = 0; } Public Final Boolean Comparasset (Boolean Expect, Boolean Update) {int e = attendre? 1: 0; int u = mise à jour? 1: 0; Renvoie unpape.compareAndSwapint (ceci, ValueOffset, E, U); }}Cela fait partie du code d'Atomicboolan, et nous voyons plusieurs méthodes et propriétés clés ici.
1. L'objet Sun.Misc.unsafe est utilisé. Cette classe fournit une série de méthodes pour faire fonctionner directement des objets de mémoire, mais n'est utilisé qu'en interne par JDK, et n'est pas recommandé à l'utilisation des développeurs;
2. La valeur représente la valeur réelle. Vous pouvez voir que la méthode GET juge réellement la valeur booléenne en fonction de la question de savoir si la valeur est égale à 0. La valeur ici est définie comme volatile, car volatile peut garantir la visibilité de la mémoire, c'est-à-dire que la valeur de la valeur change, d'autres threads peuvent voir la valeur modifiée immédiatement. Le prochain article parlera de la visibilité de volatile, bienvenue à suivre
3. ValueOffset est le décalage de mémoire de la valeur de valeur, obtenu en utilisant la méthode danget.ObjectFieldOffset et utilisé comme méthode de comparaison suivante;
4. Méthode ComparandDset, il s'agit de la méthode de base de la mise en œuvre de CAS. Lorsque vous utilisez la méthode atomicboolean, vous n'avez qu'à passer la valeur attendue et la valeur à mettre à jour. La méthode dangereuse.compareAndSwapint (cette, valeur Offset, E, U) est appelée. Il s'agit d'une méthode native, implémentée en C ++, et le code spécifique ne sera pas publié. En bref, il utilise l'instruction CMMPXCHG du CPU pour terminer la comparaison et le remplacement. Bien sûr, en fonction de la version spécifique du système, il existe également des différences de mise en œuvre. Ceux qui sont intéressés peuvent rechercher les articles pertinents par eux-mêmes.
Utiliser des scénarios
Par exemple, Atomicboolean peut être utilisé dans un tel scénario. Le système doit déterminer si certaines opérations d'initialisation doivent être effectuées en fonction des propriétés d'état d'une variable booléenne. S'il s'agit d'un environnement multi-thread et éviter les exécutions répétées, il peut être implémenté à l'aide d'Atomicboolean. Le pseudocode est le suivant:
Flag atomicboolien statique final privé = new atomicboolean (); if (Flag.compareAndSet (false, true)) {init (); }Par exemple, AtomicInteger peut être utilisé dans les compteurs et dans des environnements multi-thread pour assurer un comptage précis.
Questions ABA
Il y a un problème avec CAS, c'est-à-dire qu'une valeur passe de A à B puis de B à A. Dans ce cas, CAS pensera que la valeur n'a pas changé, mais en fait, elle a changé. À cet égard, il y a atomicStampEDReDeference sous des paquets simultanés qui fournit une implémentation basée sur le numéro de version, qui peut résoudre certains problèmes.
Résumer
Ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.