You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 11:55:26