0. 뮤텍스 잠금에 대하여
소위 뮤텍스 잠금은 한 번에 하나의 스레드만 보유할 수 있는 잠금을 의미합니다. jdk1.5 이전에는 일반적으로 여러 제어를 위해 동기화 메커니즘을 사용했습니다. 스레드 공유 리소스에 대한 액세스 이제 잠금은 동기화 메커니즘보다 더 넓은 범위의 잠금 작업을 제공합니다. 잠금과 동기화 메커니즘의 주요 차이점은 다음과 같습니다.
동기화 메커니즘은 각 객체와 관련된 암시적 모니터 잠금에 대한 액세스를 제공합니다. 모든 잠금 획득 및 해제가 블록 구조에 나타나도록 합니다. 여러 잠금이 획득되면 동기화된 메커니즘은 스레드 실행 코드가 동기화된 명령문 블록의 범위를 초과하는 한 암시적으로 잠금을 해제해야 합니다. 잠금 메커니즘은 잠금을 해제하기 위해 명시적으로 Lock 개체의 Unlock() 메서드를 호출해야 합니다. 이는 잠금을 획득하는 것과 잠금을 해제하는 것이 동일한 블록 구조에 나타나지 않는다는 것을 의미합니다. 업데이트 자물쇠 무료 주문이 가능합니다.
1. ReentrantLock 소개
ReentrantLock은 "배타적 잠금"이라고도 알려진 재진입 뮤텍스 잠금입니다.
이름에서 알 수 있듯이 ReentrantLock 잠금은 동일한 시점에 하나의 스레드에서만 보유할 수 있으며 재진입은 단일 스레드에서 ReentrantLock 잠금을 여러 번 획득할 수 있음을 의미합니다.
ReentrantLock은 "공정한 잠금"과 "불공정한 잠금"으로 구분됩니다. 그 차이는 잠금 획득 메커니즘이 공정한지 여부에 반영됩니다. "Lock"은 경쟁하는 리소스를 보호하고 동시에 스레드를 작동하는 여러 스레드로 인해 발생하는 오류를 방지하기 위한 것입니다. ReentrantLock은 동일한 시점에 하나의 스레드에서만 획득할 수 있습니다(스레드가 "잠금"을 획득하면 다른 스레드가 대기해야 함). ); 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) Depot은 창고입니다. 생산()을 통해 창고에서 상품을 생산할 수 있고, 소비()를 통해 창고에 있는 상품을 소비할 수 있습니다. 배타적 잠금을 통해 창고에 대한 상호 배타적인 접근이 이루어집니다. 창고에서 물품을 운영(생산/소비)하기 전에는 lock()을 통해 창고를 잠그고, 작업 후에는 Unlock()을 통해 잠금을 해제합니다.
(2) 프로듀서는 프로듀서 클래스입니다. 창고에서 제품을 생산하기 위한 새로운 스레드를 생성하려면 Producer에서 producer() 함수를 호출하세요.
(3) 고객은 소비자 계층입니다. Customer에서 Consumer() 함수를 호출하여 창고의 제품을 소비하는 새 스레드를 생성합니다.
(4) 메인 스레드 메인에서는 새로운 생산자 mPro와 새로운 소비자 mCus를 생성합니다. 그들은 각각 창고에서 제품을 생산/소비합니다.
메인의 생산/소비 수량에 따라 최종적으로 창고에 남은 제품은 50개 입니다. 실행 결과는 우리의 기대와 일치합니다!
이 모델에는 두 가지 문제가 있습니다.
(1) 실제로 창고의 용량은 음수가 될 수 없습니다. 그러나 이 모델의 창고 용량은 음수일 수 있으며 이는 현실과 모순됩니다!
(2) 현실적으로 창고 용량은 제한되어 있습니다. 하지만 이 모델의 용량에는 실제로 제한이 없습니다!
잠시 후에 이 두 가지 문제를 해결하는 방법에 대해 이야기하겠습니다. 이제 간단한 예제 2를 살펴보겠습니다. "예제 1"과 "예제 2"를 비교하면 lock() 및 Unlock()의 사용을 더 명확하게 이해할 수 있습니다.
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"의 두 가지 문제인 "창고의 용량은 음수가 될 수 없습니다."와 "창고의 용량은 창고가 제한되어 있습니다.”
이 문제의 해결 방법은 조건을 통해서입니다. 조건은 Lock과 함께 사용해야 합니다. Condition의 wait() 메서드를 통해 스레드를 차단할 수 있습니다. [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 multi에 대한 자세한 설명 -스레드 프로그래밍 ReentrantLock 클래스 사용법에 대한 관련 기사는 PHP 중국어 웹사이트를 참고하세요!