0. À propos de Mutex
Le soi-disant verrouillage mutex fait référence à une serrure qui ne peut avoir qu'un seul fil à la fois. Avant JDK1.5, nous avons généralement utilisé le mécanisme synchronisé pour contrôler l'accès de plusieurs threads aux ressources partagées. Maintenant, Lock offre une gamme plus large d'opérations de verrouillage que le mécanisme synchronisé. Les principales différences entre les mécanismes de verrouillage et synchronisés:
Le mécanisme synchronisé donne accès aux verrous de moniteur implicites associés à chaque objet, et force toutes les acquisitions et la libération de verrouillage pour apparaître dans une structure de bloc. Lorsque plusieurs verrous sont acquis, ils doivent être libérés dans l'ordre inverse. Le mécanisme synchronisé libère implicitement les verrous. Tant que le code s'exécute par le thread dépasse la portée du bloc d'instruction synchronisé, le verrou sera libéré. Le mécanisme de verrouillage doit appeler explicitement la méthode déverrouillée () de l'objet de verrouillage pour libérer le verrou, qui offre la possibilité que l'acquisition et la libération des verrous n'apparaissent pas dans la même structure de bloc et relâchent les verrous dans un ordre plus libre.
1. Introduction à Reentrantlock
Reentrantlock est un serrure de mutex réentrant, également connu sous le nom de "verrouillage exclusif".
Comme son nom l'indique, un verrou ReentrantLock ne peut être maintenu que par un seul verrouillage au même moment; tandis que le réentrant signifie qu'un verrou de reentrantlock peut être acquis plusieurs fois par un seul thread.
Reentrantlock est divisé en "Fair Lock" et "Lock injuste". Leurs différences se reflètent dans la question de savoir si le mécanisme d'obtention de verrous est juste. "Lock" consiste à protéger les ressources concurrentes et à empêcher plusieurs threads de faire fonctionner des threads en même temps et des erreurs. Reentrantlock ne peut être acquis que par un seul thread en même temps (lorsqu'un thread acquiert le "verrouillage", les autres threads doivent attendre); Reentraantlock gère tous les threads qui acquièrent la serrure via une file d'attente FIFO. Sous le mécanisme de "Fair Lock", la file d'attente des threads pour acquérir la serrure en séquence; Alors que "Lock non-FAIR" acquiert la serrure, qu'elle soit ou non au début de la file d'attente.
Liste de fonctions reentrantlock
// Créez un RentrantLoc, qui est "Lock délât" par défaut. Reentrantlock () // La politique de création est le reentrantlock de la foire. Si juste est vrai, cela signifie que c'est une serrure équitable, et si la juste est fausse, cela signifie qu'il s'agit d'une serrure non-fair. ReentrantLock (booléen salon) // interroge le nombre de fois que le thread actuel a maintenu cette serrure. int getholdcount () // renvoie le thread qui possède actuellement ce verrou et si ce verrou ne appartient à aucun thread, renvoyez NULL. Protected Thread Outowner () // Renvoie une collection contenant le fil qui peut attendre pour acquérir ce verrou. Collection protégée <fiter> getqueUedThreads () // Renvoie le nombre estimé de threads en attente d'acquérir ce verrou. int getqueuelngth () // renvoie une collection qui contient les threads qui peuvent attendre une condition donnée liée à ce verrou. Collection protégée <fiter> getWaitingThreads (condition de condition) // Renvoie l'estimation du thread en attendant la condition donnée associée à cette serrure. int getWaitqueUeUngth (condition de condition) // demande si le fil donné attend d'acquérir ce verrou. Boolean HasqueUedThread (Thread Thread) // demande si certains fils attendent d'acquérir ce verrou. Boolean HasqueUedThreads () // interroge si certains fils attendent une condition donnée liée à ce verrou. Boolean Haswaiters (condition de condition) // Renvoie True s'il s'agit de "LOCK LOCK", sinon renvoyez false. Boolean isfair () // demande si le thread actuel maintient cette serrure. Booléen IsheldByCurrentThread () // demande si cette serrure est maintenue par un fil. Boolean islocked () // Obtenez le verrou. Void Lock () // Si le fil actuel n'est pas interrompu, le verrou est acquis. void lockinterruptible () // renvoie l'instance de condition utilisée pour utiliser avec cette instance de verrouillage. Condition newCondition () // n'acquiert le verrou que s'il n'est pas maintenu par un autre fil pendant l'appel. booléen trylock () // Si le verrou n'est pas maintenu par un autre thread dans un temps d'attente donné et que le fil actuel n'est pas interrompu, le verrou est acquis. Boolean Trylock (temps de temps long, unité TimeUnit) // Essayez de libérer ce verrou. void unlock ()
2. Exemple de reentrantlock
En comparant "Exemple 1" et "Exemple 2", nous pouvons clairement comprendre le rôle du verrouillage et du déverrouillage
2.1 Exemple 1
import java.util.concurrent.locks.lock; import java.util.concurrent.locks.reentrantlock; // locktest1.java// classe de référentiel Depot {private int size; // le nombre réel de verrouillage privé du référentiel; // Lock exclusif public Depot () {this.size = 0; this.lock = new reentrantLock (); } public void produce (int val) {lock.lock (); essayez {size + = val; System.out.printf ("% s produce (% d) -> size =% d / n", thread.currentThread (). GetName (), val, size); } enfin {lock.unlock (); }} public void consommation (int val) {lock.lock (); essayez {size - = val; System.out.printf ("% S Consommation (% D) <- SIZE =% D / N", Thread.CurrentThread (). GetName (), Val, Size); } enfin {lock.unlock (); }}}; // Producteur Producteur {dépôt de dépôt privé; Producteur public (Depot Depot) {this.depot = dépôt; } // Produits de consommation: Créez un nouveau fil pour produire des produits dans l'entrepôt. public void produce (final int Val) {new Thread () {public void run () {dépôt.produce (val); } }.commencer(); }} // Client de classe Client {dépôt de dépôt privé; Customer public (Depot Depot) {this.depot = dépôt; } // Produit de consommation: Créez un nouveau fil pour consommer des produits à partir de l'entrepôt. Consommation publique de void (final int Val) {new Thread () {public void run () {depot.consume (val); } }.commencer(); }} public class LockTest1 {public static void main (String [] args) {Depot mdepot = new depot (); Producteur MPRO = nouveau producteur (MDEPOT); Client MCUS = nouveau client (MDEPOT); mpro.produce (60); mpro.produce (120); mcus.consume (90); mcus.consume (150); mpro.produce (110); }} Résultats en cours:
Thread-0 produit (60) -> size = 60thread-1 produit (120) -> size = 180thread-3 consommer (150) <- size = 30thread-2 consommer (90) <- size = -60thread-4 produce (110) -> size = 50
Analyse des résultats:
(1) Le dépôt est un entrepôt. Les marchandises peuvent être produites dans l'entrepôt par le biais de produits (), et les marchandises de l'entrepôt peuvent être consommées par la consommation (). L'accès mutuellement exclusif à l'entrepôt est réalisé via le verrouillage exclusif: avant d'exploiter les marchandises dans l'entrepôt (production / consommation), l'entrepôt sera verrouillé via Lock () d'abord, puis déverrouillé via unlock () une fois l'opération terminée.
(2) Le producteur est producteur. Appeler la fonction Produce () dans le producteur peut créer un nouveau fil pour produire des produits dans l'entrepôt.
(3) Le client est une catégorie de consommation. L'appel de la fonction Consommation () chez le client peut créer un nouveau produit de consommation de thread dans l'entrepôt.
(4) Dans le fil principal principal, nous créerons un nouveau producteur MPRO et un nouveau MCU consommateur. Ils produisent / consomment des produits dans les entrepôts respectivement.
Selon la quantité de production / consommation dans la principale, le produit final restant dans l'entrepôt devrait être de 50. Les résultats de l'opération sont conformes à nos attentes!
Il y a deux problèmes avec ce modèle:
(1) En réalité, la capacité de l'entrepôt ne peut pas être négative. Cependant, la capacité de l'entrepôt de ce modèle peut être négative, ce qui contredit la réalité!
(2) En réalité, la capacité de l'entrepôt est limitée. Cependant, il n'y a vraiment aucune limite à la capacité de ce modèle!
Nous parlerons brièvement de la façon de résoudre ces deux problèmes. Voyons maintenant d'abord un exemple simple 2; En comparant "Exemple 1" et "Exemple 2", nous pouvons comprendre le but de Lock () et Deverlock () plus clairement.
2.2 Exemple 2
import java.util.concurrent.locks.lock; import java.util.concurrent.locks.reentrantlock; // locktest2.java// classe de référentiel Depot {private int size; // le nombre réel de verrouillage privé du référentiel; // Lock exclusif public Depot () {this.size = 0; this.lock = new reentrantLock (); } public void produce (int val) {// lock.lock (); // try {size + = val; System.out.printf ("% s produce (% d) -> size =% d / n", thread.currentThread (). GetName (), val, size); //} catch (interruptedException e) {//} enfin {// lock.unlock (); //}} public Void consommation (int Val) {// Lock.Lock (); // try {Taille - = Val; System.out.printf ("% s Consume (% d) <- size =% d / n", thread.currentThread (). GetName (), val, size); //} enfin {// lock.unlock (); //}}}; // producteur producteur producteur {dépôt de dépôt privé; Producteur public (dépôt de dépôt) {this.depot = dépôt; } // Produit de consommation: créez un nouveau fil pour produire le produit dans l'entrepôt. public void produce (final int Val) {new Thread () {public void run () {dépôt.produce (val); } }.commencer(); }} // Client de classe Client {dépôt de dépôt privé; Customer public (Depot Depot) {this.depot = dépôt; } // Produit de consommation: Créez un nouveau fil pour consommer des produits à partir de l'entrepôt. Consommation publique de void (final int Val) {new Thread () {public void run () {depot.consume (val); } }.commencer(); }} public class LockTest2 {public static void main (String [] args) {depot mdepot = new depot (); Producteur MPRO = nouveau producteur (MDEPOT); Client MCUS = nouveau client (MDEPOT); mpro.produce (60); mpro.produce (120); mcus.consume (90); mcus.consume (150); mpro.produce (110); }} (Une fois) Résultat:
Thread-0 Produce (60) -> size = -60thread-4 product (110) -> size = 50thread-2 consommation (90) <- size = -60thread-1 produit (120) -> size = -60thread-3 consommation (150) <- size = -60
Description des résultats:
"L'exemple 2" supprime le verrouillage de verrouillage en fonction de "Exemple 1". Dans l'exemple 2, le produit restant final dans l'entrepôt est de -60, pas les 50 que nous attendions. La raison en est que nous n'implémensions pas l'accès mutex au référentiel.
2.3 Exemple 3
Dans "Exemple 3", nous utilisons la condition pour résoudre deux problèmes dans "Exemple 1": "La capacité de l'entrepôt ne peut pas être négative" et "la capacité de l'entrepôt est limitée".
La solution à ce problème est par condition. La condition doit être utilisée en conjonction avec le verrouillage: la méthode Await () en condition peut faire bloquer le thread [similaire à attendre ()]; La méthode de l'état de signal () peut faire en sorte que le fil de réveil [similaire à Notify ()].
import java.util.concurrent.locks.lock; import java.util.concurrent.locks.reentrantlock; import java.util.concurrent.locks.condition; // locktest3.java// classage de travail de dépot {private int la capacité; // Capacité d'entrepôt Taille INT privée; // le nombre réel de verrouillage privé de l'entrepôt; // Construction privée de verrouillage exclusif FullCondition; // Conditions de production État privé condition vide; // Conditions de consommation Public Depot (Int Capacité) {this.capacity = capacité; this.size = 0; this.lock = new reentrantLock (); this.fullCondtion = lock.newCondition (); this.emptycondition = lock.newCondition (); } public void produce (int val) {lock.lock (); Essayez {// gauche signifie "la quantité que vous souhaitez produire" (c'est peut-être trop de production, vous devez donc produire plus) int Left = val; tandis que (gauche> 0) {// lorsque l'inventaire est plein, attendez que le "consommateur" consomme le produit. tandis que (taille> = capacité) fullCondtion.Await (); // Obtenez la "quantité de production réelle" (c'est-à-dire la nouvelle quantité ajoutée dans l'inventaire) // Si "Inventory" + "Quantité de production souhaitée"> "Capacité totale", puis "Incrément réel" = "Capacité totale" - "Capacité actuelle". (Remplissez l'entrepôt à ce moment) // sinon "incrément réel" = "La quantité que vous souhaitez produire" int inc = (taille + gauche)> Capacité? (taille de capacité): à gauche; taille + = inc; gauche - = inc; System.out.printf ("% s Produce (% 3D) -> Left =% 3D, Inc =% 3D, size =% 3d / n", thread.currentThread (). GetName (), Val, Left, Inc, size); // informer "consommateur" que vous pouvez consommer. videCondtion.signal (); }} catch (InterruptedException e) {} enfin {lock.unlock (); }} public void consommation (int val) {lock.lock (); Essayez {// gauche signifie "la quantité de consommation à consommer" (elle peut être trop grande, l'inventaire n'est pas suffisant, vous devez donc consommer plus) int Left = val; tandis que (gauche> 0) {// lorsque l'inventaire est 0, attendez que le "producteur" produise le produit. while (size <= 0) videCondtion.Await (); // Obtenez la "quantité de consommation réelle" (c'est-à-dire la diminution réelle de l'inventaire) // Si "l'inventaire" <"la quantité que le client veut consommer", alors "consommation réelle" = "inventaire"; // sinon, "consommation réelle" = "la quantité que le client veut consommer". int dec = (taille <gauche)? Taille: à gauche; taille - = déc; gauche - = déc; System.out.printf ("% S Consume (% 3D) <- Left =% 3D, DEC =% 3D, taille =% 3d / n", thread.currentThread (). GetName (), Val, Left, Dec, Size); FullCondtion.Signal (); }} catch (InterruptedException e) {} enfin {lock.unlock (); }} public String toString () {return "Capacité:" + Capacité + ", taille réelle:" + taille; }}; // producteur producteur {dépôt de dépôt privé; Producteur public (dépôt de dépôt) {this.depot = dépôt; } // Produit de consommation: créez un nouveau fil pour produire le produit dans l'entrepôt. public void produce (final int Val) {new Thread () {public void run () {dépôt.produce (val); } }.commencer(); }} // Client de classe Client {dépôt de dépôt privé; Customer public (Depot Depot) {this.depot = dépôt; } // Produit de consommation: Créez un nouveau fil pour consommer des produits à partir de l'entrepôt. Consommation publique de void (final int Val) {new Thread () {public void run () {depot.consume (val); } }.commencer(); }} public class LockTest3 {public static void main (String [] args) {Depot mdepot = new Depot (100); Producteur MPRO = nouveau producteur (MDEPOT); Client MCUS = nouveau client (MDEPOT); mpro.produce (60); mpro.produce (120); mcus.consume (90); mcus.consume (150); mpro.produce (110); }} (Une fois) Résultat:
Thread-0 Produce (60) -> Left = 0, inc = 60, taille = 60thread-1 produit (120) -> gauche = 80, inc = 40, size = 100thread-2 Consommation (90) <- Left = 0, dec = 90, taille = 10thread-3 (150) <- Left = 140, inc = 10, taille = 0thread-4 product (110) -> gauche Consommation (150) <- Left = 40, dec = 100, taille = 0Thread-4 Produce (110) -> Left = 0, Inc = 10, Size = 10Thread-3 Consume (150) <- Left = 30, dec = 10, size = 0Thread-1 Produce (120) -> Left = 0, Inc = 80, SIZE = 80thread-3 Consume (150) <- Left = 0, 0, 30, Taille = 50.