使用Reentrant Lock与Condition实现临界区的异常问题排查
多线程同步问题:ReentrantLock+Condition无法正确限制Harbour容量
你的代码存在两个关键问题,导致Car线程未正确阻塞,最终Harbour内的车辆数超过了设定的容量:
1. 条件判断使用if而非while
在carEnterHarbour方法中,你用if判断是否需要等待:
if (this.nrOfCars == this.capacity) protectNrCon.await();
当线程被signal()唤醒后,不会重新检查当前nrOfCars的状态。比如:
- 当Harbour容量已满(20辆),多个Car线程进入等待状态
- 有一辆车离开,调用
signal()唤醒其中一个等待线程 - 但在这个被唤醒的线程获取锁之前,可能已经有其他线程(比如刚执行完
carLeavingHarbour的线程,或者其他未进入等待的线程)抢先获取锁并执行了carEnterHarbour,导致nrOfCars又回到20 - 此时被唤醒的线程拿到锁后,
if条件已经不成立,但不会重新检查,直接执行nrOfCars++,导致数量超过20
正确的做法是用while循环,确保线程被唤醒后重新检查条件:
while (this.nrOfCars == this.capacity) protectNrCon.await();
2. 锁的释放未放在finally块
carEnterHarbour中,protectNr.unlock()放在了try块的末尾:
try { protectNr.lock(); // ... 逻辑代码 protectNr.unlock(); } catch (InterruptedException e) { System.out.println("Error"); }
如果await()抛出InterruptedException,会直接进入catch块,此时锁没有被释放,其他线程永远无法获取锁,造成死锁。必须将unlock()移到finally块中,确保无论是否发生异常,锁都会被释放。
修正后的Harbour类代码
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Harbour { final Lock protectNr; final Condition protectNrCon; int capacity; int nrOfCars; public Harbour(int capacity) { this.capacity = capacity; this.protectNr = new ReentrantLock(); this.protectNrCon = protectNr.newCondition(); } public void carEnterHarbour(String name) { protectNr.lock(); try { // 用while循环重新检查条件 while (this.nrOfCars == this.capacity) protectNrCon.await(); nrOfCars++; System.out.println(name + " enters"); System.out.println("Number of cars:" + this.nrOfCars); } catch (InterruptedException e) { System.out.println("Error"); // 重置中断状态 Thread.currentThread().interrupt(); } finally { // 锁放在finally块释放 protectNr.unlock(); } } public void carLeavingHarbour(String name) { protectNr.lock(); try { this.nrOfCars--; // 用signalAll()唤醒所有等待线程,避免某些线程长期饥饿 protectNrCon.signalAll(); System.out.println(name + " leaving"); System.out.println("Number of cars:" + this.nrOfCars); } finally { protectNr.unlock(); } } }
补充说明
- 将
signal()改为signalAll()是为了避免线程饥饿:如果持续用signal(),可能某些等待线程永远无法被唤醒,而signalAll()会唤醒所有等待线程,让它们重新竞争锁并检查条件,更公平。 - 不需要将
capacity改为volatile,因为所有对nrOfCars和capacity的访问都在锁的保护下,线程安全有保障。
内容的提问来源于stack exchange,提问作者Raven
相关产品推荐
相关产品推荐

