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

ReentrantLock同线程重复锁定问题:while与for循环的线程调度差异

问题解答

核心结论:不是while和for循环的执行机制有本质差异,问题出在ReentrantLock的非公平锁特性,以及两种循环下线程释放锁后的行为差异导致的调度窗口不同。

1. ReentrantLock的非公平锁逻辑

ReentrantLock默认是非公平锁,它的核心逻辑是:当线程释放锁后,不会先唤醒等待队列里的线程,而是允许当前线程(刚释放锁的线程)直接再次尝试获取锁。因为当前线程已经在CPU上运行,不需要上下文切换,所以它抢锁的成功率远高于处于等待状态的其他线程。

2. 为什么while循环能正常交替输出?

你的while循环大概率依赖共享状态标记来控制循环(比如用一个布尔变量判断是否轮到自己执行)。每次循环结束释放锁后,当前线程会进入“检查标记+等待”的逻辑——要么是自旋等待标记变化,要么是通过Condition挂起,这时候CPU会调度另一个线程运行,让它有机会抢到锁,从而实现交替输出。

举个常见的while循环示例:

private volatile boolean isPingTurn = true;
private ReentrantLock lock = new ReentrantLock();

public void ping() {
    while (true) {
        lock.lock();
        try {
            if (isPingTurn) {
                System.out.print("ping ");
                isPingTurn = false;
            }
        } finally {
            lock.unlock();
        }
    }
}

这里每次释放锁后,当前线程会再次进入while循环,检查isPingTurn,这时候因为标记已经变了,它不会输出,释放锁后会让出CPU时间片,给pong线程抢锁的机会。

3. 为什么for循环会出现线程独占锁?

for循环是固定次数的无等待循环,线程释放锁后,立刻进入下一次循环并尝试抢锁。因为非公平锁的特性,当前线程刚释放锁,处于运行状态,直接抢锁几乎每次都能成功,导致另一个线程一直处于等待队列,无法抢到锁,最终出现同一线程反复输出的情况。

比如问题中的for循环示例:

public void ping() {
    for (int i = 0; i < 10; i++) {
        lock.lock();
        try {
            System.out.print("ping ");
        } finally {
            lock.unlock();
        }
    }
}

这里每次unlock后,立刻执行下一次lock,当前线程直接抢锁成功,pong线程根本没机会介入。

4. 解决方法

  • 使用公平锁:初始化ReentrantLock时传入true,强制锁按照等待队列顺序分配:ReentrantLock lock = new ReentrantLock(true);
  • 主动让出CPU:在unlock后添加Thread.yield(),让当前线程主动放弃CPU时间片,给其他线程抢锁机会:
    finally {
        lock.unlock();
        Thread.yield();
    }
    
  • 用Condition精准控制:通过Condition实现线程的交替唤醒,彻底避免锁竞争的不确定性:
    private ReentrantLock lock = new ReentrantLock();
    private Condition pingCond = lock.newCondition();
    private Condition pongCond = lock.newCondition();
    private boolean isPingTurn = true;
    
    public void ping() throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            lock.lock();
            try {
                while (!isPingTurn) {
                    pingCond.await();
                }
                System.out.print("ping ");
                isPingTurn = false;
                pongCond.signal();
            } finally {
                lock.unlock();
            }
        }
    }
    
    public void pong() throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            lock.lock();
            try {
                while (isPingTurn) {
                    pongCond.await();
                }
                System.out.print("pong ");
                isPingTurn = true;
                pingCond.signal();
            } finally {
                lock.unlock();
            }
        }
    }
    

内容的提问来源于stack exchange,提问作者flickNuts

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 10:25:12