ホームページ >Java >&#&チュートリアル >JavaマルチスレッドプログラミングにおけるミューテックスロックReentrantLockクラスの使い方の詳細説明
0. ミューテックス ロックについて
いわゆるミューテックス ロックは、一度に最大 1 つのスレッドのみが保持できるロックを指します。jdk1.5 より前では、通常、複数のスレッドのアクセスを制御するために同期メカニズムが使用されていました。 Lock は、同期メカニズムよりも幅広いロック操作を提供します。
同期メカニズムは、各オブジェクトに関連付けられた暗黙的な監視ロックへのアクセスを提供し、すべてのロックの取得と解放を強制します。複数のロックが取得された場合、スレッドによって実行されるコードが同期されたステートメント ブロックの範囲を超える限り、ロックは逆の順序で解放される必要があります。 Lock メカニズムは、ロックを解放するために、Lock オブジェクトの lock() メソッドを明示的に呼び出す必要があります。これにより、同じブロック構造に現れずにロックを取得および解放できるようになります。より自由な注文。
1. ReentrantLock の概要
ReentrantLock は、「排他的ロック」とも呼ばれるリエントラントなミューテックス ロックです。
名前が示すように、ReentrantLock は同時に 1 つのスレッドによってのみ保持できます。リエントラントとは、単一のスレッドによって ReentrantLock を複数回取得できることを意味します。
ReentrantLockは「公平なロック」と「不公平なロック」に分けられます。この違いは、ロック取得メカニズムが公平であるかどうかに反映されます。 「ロック」は、競合するリソースを保護し、複数のスレッドが同時にスレッドを操作することによって発生するエラーを防ぐためのものです。 ReentrantLock は、同時に 1 つのスレッドによってのみ取得できます (スレッドが「ロック」を取得すると、他のスレッドは待機する必要があります)。 ); ReentrantLock ロックを取得するすべてのスレッドは、FIFO 待機キューを通じて管理されます。 「公平なロック」メカニズムでは、スレッドは順番にロックを取得するためにキューに入れられますが、「不公平なロック」では、ロックが取得可能になると、スレッドはキューの先頭にあるかどうかに関係なくロックを取得します。
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. ReentrantLockの例
「例1」と「例2」を比較すると、ロックとロック解除の役割がよくわかります
2.1 例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); } }
実行結果:
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
結果分析:
(1) デポは倉庫です。商品は、product() を通じて倉庫で生産でき、倉庫内の商品は、consum() を通じて消費できます。倉庫への相互排他的アクセスは、排他的ロックによって実現されます。倉庫内で商品を操作 (生産/消費) する前に、倉庫は lock() によってロックされ、操作後に lock() によってロックが解除されます。
(2) プロデューサーはプロデューサークラスです。プロデューサーでProduce()関数を呼び出して、倉庫で製品を生産するための新しいスレッドを作成します。
(3) 顧客は消費者クラスです。 Customer で Consumer() 関数を呼び出して、倉庫内の製品を消費するための新しいスレッドを作成します。
(4) メインスレッド main で、新しいプロデューサー mPro と新しいコンシューマー mCus を作成します。彼らはそれぞれ倉庫に製品を生産/消費します。
メインの生産/消費量によると、倉庫に最終的に残る製品は50個になるはずです。実行結果は私たちの期待と一致しています。
このモデルには 2 つの問題があります:
(1) 実際には、倉庫の容量を負の数にすることはできません。ただし、このモデルの倉庫容量はマイナスになる可能性があり、これは現実と矛盾します。
(2)現実的には倉庫の容量には限りがあります。しかし、このモデルには本当に容量の制限がありません。
これら 2 つの問題を解決する方法については後ほど説明します。次に、簡単な例 2 を見てみましょう。「例 1」と「例 2」を比較すると、lock() と lock() の使用法がより明確に理解できます。
2.2 例 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); } }
(ある時点) 実行結果:
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
結果の説明:
「例 2」は、「例 1」に基づいてロックを削除します。 「例 2」では、倉庫に最終的に残っている製品は、予想した 50 個ではなく、-60 個です。理由は、倉庫への相互排他アクセスを実装していないためです。
2.3 例 3
「例 3」では、「例 1」の 2 つの問題、「倉庫の容量は負の数にできない」と「倉庫の容量は制限されている」を条件を使用して解決します。
この問題の解決策は、Condition を使用することです。 Condition は Lock と組み合わせて使用する必要があります。Condition の await() メソッドを使用すると、[wait() と同様に] スレッドをブロックできます。Condition の signal() メソッドを使用すると、スレッドを起動できます [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); } }
(ある時点) 実行結果:
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
Java マルチスレッド プログラミングにおけるミューテックス ロック ReentrantLock クラスの使用法に関する詳細な説明については、関連する PHP の中国語 Web サイトを参照してください。記事!