0. À propos des verrous mutex
Le soi-disant verrou mutex fait référence à un verrou qui ne peut être détenu que par un seul thread à la fois. Avant jdk1.5, nous utilisions généralement le mécanisme synchronisé pour en contrôler plusieurs. threads Accès aux ressources partagées. Désormais, Lock offre une gamme d'opérations de verrouillage plus large que le mécanisme synchronisé. La principale différence entre Lock et le mécanisme synchronisé :
Le mécanisme synchronisé donne accès au verrouillage implicite du moniteur associé à chaque objet, et force toutes les acquisitions et libérations de verrous à apparaître dans une structure de blocs Lorsque plusieurs verrous sont acquis, ils doivent être libérés dans l'ordre inverse. Le mécanisme synchronisé libère le verrou implicitement, tant que le code en cours d'exécution dépasse Once la portée de l'instruction synchronisée. Le bloc est dépassé, le verrou sera libéré. Le mécanisme Lock doit appeler explicitement la méthode unlock() de l'objet Lock pour libérer le verrou. Cela signifie que l'acquisition du verrou et la libération du verrou n'apparaissent pas dans la même structure de bloc, et pour mettre à jour La commande gratuite de serrures est possible.
1. Introduction à ReentrantLock
ReentrantLock est un verrou mutex réentrant, également connu sous le nom de « verrou exclusif ».
Comme son nom l'indique, le verrou ReentrantLock ne peut être détenu que par un seul thread au même moment ; et réentrant signifie que le verrou ReentrantLock peut être acquis plusieurs fois par un seul thread.
ReentrantLock est divisé en « verrouillage équitable » et « verrouillage injuste ». Leur différence se reflète dans la question de savoir si le mécanisme d'acquisition du verrou est équitable. "Lock" vise à protéger les ressources concurrentes et à éviter les erreurs causées par plusieurs threads exécutant des threads en même temps. ReentrantLock ne peut être acquis que par un thread au même moment (lorsqu'un thread acquiert le "lock", les autres threads doivent attendre). ); ReentrantLock Tous les threads qui acquièrent le verrou sont gérés via une file d'attente FIFO. Dans le cadre du mécanisme de « verrouillage équitable », les threads se mettent en file d'attente pour acquérir les verrous dans l'ordre ; tandis qu'en mode « verrouillage injuste », lorsque le verrou est disponible pour l'acquisition, les threads acquièrent le verrou, qu'ils soient ou non au début de la file d'attente.
Liste des fonctions ReentrantLock
// 创建一个 ReentrantLock ,默认是“非公平锁”。 ReentrantLock() // 创建策略是fair的 ReentrantLock。fair为true表示是公平锁,fair为false表示是非公平锁。 ReentrantLock(boolean fair) // 查询当前线程保持此锁的次数。 int getHoldCount() // 返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。 protected Thread getOwner() // 返回一个 collection,它包含可能正等待获取此锁的线程。 protected Collection<Thread> getQueuedThreads() // 返回正等待获取此锁的线程估计数。 int getQueueLength() // 返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。 protected Collection<Thread> getWaitingThreads(Condition condition) // 返回等待与此锁相关的给定条件的线程估计数。 int getWaitQueueLength(Condition condition) // 查询给定线程是否正在等待获取此锁。 boolean hasQueuedThread(Thread thread) // 查询是否有些线程正在等待获取此锁。 boolean hasQueuedThreads() // 查询是否有些线程正在等待与此锁有关的给定条件。 boolean hasWaiters(Condition condition) // 如果是“公平锁”返回true,否则返回false。 boolean isFair() // 查询当前线程是否保持此锁。 boolean isHeldByCurrentThread() // 查询此锁是否由任意线程保持。 boolean isLocked() // 获取锁。 void lock() // 如果当前线程未被中断,则获取锁。 void lockInterruptibly() // 返回用来与此 Lock 实例一起使用的 Condition 实例。 Condition newCondition() // 仅在调用时锁未被另一个线程保持的情况下,才获取该锁。 boolean tryLock() // 如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。 boolean tryLock(long timeout, TimeUnit unit) // 试图释放此锁。 void unlock()
2. Exemple de ReentrantLock
En comparant "Exemple 1" et "Exemple 2", nous pouvons clairement comprendre les fonctions de verrouillage et de déverrouillage
2.1 Exemple 1
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; // LockTest1.java // 仓库 class Depot { private int size; // 仓库的实际数量 private Lock lock; // 独占锁 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); } finally { lock.unlock(); } } public void consume(int val) { lock.lock(); try { size -= val; System.out.printf("%s consume(%d) <-- size=%d\n", Thread.currentThread().getName(), val, size); } finally { lock.unlock(); } } }; // 生产者 class Producer { private Depot depot; public Producer(Depot depot) { this.depot = depot; } // 消费产品:新建一个线程向仓库中生产产品。 public void produce(final int val) { new Thread() { public void run() { depot.produce(val); } }.start(); } } // 消费者 class Customer { private Depot depot; public Customer(Depot depot) { this.depot = depot; } // 消费产品:新建一个线程从仓库中消费产品。 public void consume(final int val) { new Thread() { public void run() { depot.consume(val); } }.start(); } } public class LockTest1 { public static void main(String[] args) { Depot mDepot = new Depot(); Producer mPro = new Producer(mDepot); Customer mCus = new Customer(mDepot); mPro.produce(60); mPro.produce(120); mCus.consume(90); mCus.consume(150); mPro.produce(110); } }
Résultat d'exécution :
Thread-0 produce(60) --> size=60 Thread-1 produce(120) --> size=180 Thread-3 consume(150) <-- size=30 Thread-2 consume(90) <-- size=-60 Thread-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 via Produce(), et les marchandises dans l'entrepôt peuvent être consommées via consume(). L'accès mutuellement exclusif à l'entrepôt est obtenu grâce au verrouillage exclusif : avant d'exploiter les marchandises (production/consommation) dans l'entrepôt, l'entrepôt sera verrouillé via lock(), puis déverrouillé via unlock() après l'opération.
(2) Producteur est la classe producteur. Appelez la fonction Produce() dans Producer pour créer un nouveau thread pour produire des produits dans l'entrepôt.
(3) Le client est la classe des consommateurs. Appelez la fonction consume() dans Customer pour créer un nouveau thread pour consommer les produits dans l’entrepôt.
(4) Dans le fil de discussion principal main, nous allons créer un nouveau producteur mPro et un nouveau consommateur mCus. Ils produisent/consomment respectivement des produits dans l’entrepôt.
Selon la quantité principale de production/consommation, le nombre final de produits restants dans l'entrepôt devrait être de 50. Les résultats en cours d'exécution sont à la hauteur de 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 un nombre négatif. Cependant, la capacité de l’entrepôt dans 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 de la façon de résoudre ces deux problèmes dans un instant. Maintenant, regardons un exemple simple 2 ; en comparant « exemple 1 » et « exemple 2 », nous pouvons comprendre plus clairement les utilisations de lock() et unlock().
2.2 Exemple 2
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; // LockTest2.java // 仓库 class Depot { private int size; // 仓库的实际数量 private Lock lock; // 独占锁 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) { // } finally { // lock.unlock(); // } } public void consume(int val) { // lock.lock(); // try { size -= val; System.out.printf("%s consume(%d) <-- size=%d\n", Thread.currentThread().getName(), val, size); // } finally { // lock.unlock(); // } } }; // 生产者 class Producer { private Depot depot; public Producer(Depot depot) { this.depot = depot; } // 消费产品:新建一个线程向仓库中生产产品。 public void produce(final int val) { new Thread() { public void run() { depot.produce(val); } }.start(); } } // 消费者 class Customer { private Depot depot; public Customer(Depot depot) { this.depot = depot; } // 消费产品:新建一个线程从仓库中消费产品。 public void consume(final int val) { new Thread() { public void run() { depot.consume(val); } }.start(); } } public class LockTest2 { public static void main(String[] args) { Depot mDepot = new Depot(); Producer mPro = new Producer(mDepot); Customer mCus = new Customer(mDepot); mPro.produce(60); mPro.produce(120); mCus.consume(90); mCus.consume(150); mPro.produce(110); } }
(une certaine heure) Résultats de course :
Thread-0 produce(60) --> size=-60 Thread-4 produce(110) --> size=50 Thread-2 consume(90) <-- size=-60 Thread-1 produce(120) --> size=-60 Thread-3 consume(150) <-- size=-60
Description du résultat :
"Exemple 2" supprime le verrou basé sur "Exemple 1". Dans "Exemple 2", le produit final restant dans l'entrepôt est de -60, et non des 50 attendus. La raison en est que nous n’avons pas mis en place un accès mutuellement exclusif à l’entrepôt.
2.3 Exemple 3
Dans "Exemple 3", nous utilisons Condition pour résoudre les deux problèmes de "Exemple 1" : "La capacité de l'entrepôt ne peut pas être un nombre négatif" et "La capacité de l'entrepôt l’entrepôt est restreint ».
La solution à ce problème passe par Condition. Condition doit être utilisée conjointement avec Lock : via la méthode wait() dans Condition, le thread peut être bloqué [similaire à wait()] via la méthode signal() de Condition, le thread peut être réveillé [similaire à notify" ; ()].
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Condition; // LockTest3.java // 仓库 class Depot { private int capacity; // 仓库的容量 private int size; // 仓库的实际数量 private Lock lock; // 独占锁 private Condition fullCondtion; // 生产条件 private Condition emptyCondtion; // 消费条件 public Depot(int capacity) { this.capacity = capacity; this.size = 0; this.lock = new ReentrantLock(); this.fullCondtion = lock.newCondition(); this.emptyCondtion = lock.newCondition(); } public void produce(int val) { lock.lock(); try { // left 表示“想要生产的数量”(有可能生产量太多,需多此生产) int left = val; while (left > 0) { // 库存已满时,等待“消费者”消费产品。 while (size >= capacity) fullCondtion.await(); // 获取“实际生产的数量”(即库存中新增的数量) // 如果“库存”+“想要生产的数量”>“总的容量”,则“实际增量”=“总的容量”-“当前容量”。(此时填满仓库) // 否则“实际增量”=“想要生产的数量” int inc = (size+left)>capacity ? (capacity-size) : left; size += inc; left -= inc; System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d\n", Thread.currentThread().getName(), val, left, inc, size); // 通知“消费者”可以消费了。 emptyCondtion.signal(); } } catch (InterruptedException e) { } finally { lock.unlock(); } } public void consume(int val) { lock.lock(); try { // left 表示“客户要消费数量”(有可能消费量太大,库存不够,需多此消费) int left = val; while (left > 0) { // 库存为0时,等待“生产者”生产产品。 while (size <= 0) emptyCondtion.await(); // 获取“实际消费的数量”(即库存中实际减少的数量) // 如果“库存”<“客户要消费的数量”,则“实际消费量”=“库存”; // 否则,“实际消费量”=“客户要消费的数量”。 int dec = (size<left) ? size : left; size -= dec; left -= dec; System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d\n", Thread.currentThread().getName(), val, left, dec, size); fullCondtion.signal(); } } catch (InterruptedException e) { } finally { lock.unlock(); } } public String toString() { return "capacity:"+capacity+", actual size:"+size; } }; // 生产者 class Producer { private Depot depot; public Producer(Depot depot) { this.depot = depot; } // 消费产品:新建一个线程向仓库中生产产品。 public void produce(final int val) { new Thread() { public void run() { depot.produce(val); } }.start(); } } // 消费者 class Customer { private Depot depot; public Customer(Depot depot) { this.depot = depot; } // 消费产品:新建一个线程从仓库中消费产品。 public void consume(final int val) { new Thread() { public void run() { depot.consume(val); } }.start(); } } public class LockTest3 { public static void main(String[] args) { Depot mDepot = new Depot(100); Producer mPro = new Producer(mDepot); Customer mCus = new Customer(mDepot); mPro.produce(60); mPro.produce(120); mCus.consume(90); mCus.consume(150); mPro.produce(110); } }
(un certain temps) résultat d'exécution :
Thread-0 produce( 60) --> left= 0, inc= 60, size= 60 Thread-1 produce(120) --> left= 80, inc= 40, size=100 Thread-2 consume( 90) <-- left= 0, dec= 90, size= 10 Thread-3 consume(150) <-- left=140, dec= 10, size= 0 Thread-4 produce(110) --> left= 10, inc=100, size=100 Thread-3 consume(150) <-- left= 40, dec=100, size= 0 Thread-4 produce(110) --> left= 0, inc= 10, size= 10 Thread-3 consume(150) <-- left= 30, dec= 10, size= 0 Thread-1 produce(120) --> left= 0, inc= 80, size= 80 Thread-3 consume(150) <-- left= 0, dec= 30, size= 50
Explication plus détaillée de Java multi -thread programmation Pour les articles connexes sur l'utilisation de la classe ReentrantLock, veuillez faire attention au site Web PHP chinois !