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

LeetCode 1116 Java并发问题:wait后线程是否会重检代码?

问题描述

我实现的LeetCode 1116题ZeroEvenOdd代码原本运行正常,但把synchronized代码块里的while判断改成if判断后,出现了odd方法输出偶数、even方法输出奇数的异常。我发现用while时,notifyAll后拿到锁的线程会重新检查mode变量;换成if的话,线程直接从wait处继续执行,不会重新检查mode。我查资料知道notifyAll后线程是从wait处恢复执行,不会回到wait之前的代码,但这里while却能让线程重新检查mode,想搞懂这是为什么。

代码实现

class ZeroEvenOdd {
    private int n;
    private AtomicInteger counter;
    private AtomicInteger mode;
    private Object lock;
    
    public ZeroEvenOdd(int n) {
        this.n = n;
        this.counter = new AtomicInteger(0);
        this.mode = new AtomicInteger(0);
        this.lock = new Object();

    }

    public void zero(IntConsumer printNumber) throws InterruptedException {
        while(counter.get() < n){
            synchronized(lock){
                // if I change this line to 
                // if(mode.get() == 1 || mode.get() == 3){lock.wait();}
                // then everything goes wrong
                while(mode.get() == 1 || mode.get() == 3){lock.wait();}

                if(counter.get() < n){
                    printNumber.accept(0);}
                    mode.incrementAndGet();
                    lock.notifyAll();
            }
        }
    }

    public void even(IntConsumer printNumber) throws InterruptedException {
        while(counter.get() < n){
            synchronized(lock){
                 // if I change this line to 
                // if(mode.get() != 3){lock.wait();}
                // then everything goes wrong
                while(mode.get() != 3){lock.wait();}
                if(counter.get() < n){
                    printNumber.accept(counter.incrementAndGet());}
                mode.set(0);
                lock.notifyAll();
            }
            
        }
        
    }

    public void odd(IntConsumer printNumber) throws InterruptedException {
        while(counter.get() < n){
            synchronized(lock){
                 // if I change this line to 
                // if(mode.get() != 1){lock.wait();}
                // then everything goes wrong
                while(mode.get() != 1){lock.wait();}
                if(counter.get() < n){
                    printNumber.accept(counter.incrementAndGet());}
                mode.incrementAndGet();
                lock.notifyAll();
                
            }

        }
        
    }
}
原因解析

核心原因在于线程被唤醒后必须重新检查条件,while和if的本质区别就是是否做二次校验:

  • while循环的逻辑:
    当线程被notifyAll唤醒并重新获得锁后,会回到while循环的起始位置,再次执行条件判断。如果此时mode的状态不符合当前线程的执行要求,线程会再次进入wait状态,保证只有满足条件的线程才能继续执行后续代码。
    比如zero线程被唤醒后,会重新检查mode是不是1或3,如果是,就继续等待;如果不是,才执行打印0的逻辑。

  • if判断的问题:
    if只会在进入synchronized块时检查一次条件。如果线程因为条件不满足进入wait,被notifyAll唤醒后,会直接跳过if判断,继续执行wait之后的代码——完全不管此时mode的状态是否变化、是否符合当前线程的执行要求。
    举个例子:假设odd线程因为mode !=1进入wait,之后zero线程执行完把mode改成1并调用notifyAll。如果此时even线程先抢到锁,它之前的if(mode !=3)判断已经通过(当时mode是0),被唤醒后直接跳过判断执行打印逻辑,就会导致偶数线程打印奇数,出现异常。

  • 为什么while能重新检查?
    你查的资料没错:线程被唤醒后确实是从wait()方法的位置恢复执行,但while循环的结构让它恢复后,会再次执行循环条件的判断——相当于把条件检查逻辑重复了一遍。wait()之后的代码是循环体的结束},然后回到while的开头重新判断条件,这完全符合Java线程的执行逻辑。

简单来说,while是循环等待+条件校验,确保线程每次被唤醒后都确认自己该执行了;if是单次判断+等待,一旦被唤醒就不管条件直接执行,必然会出现线程执行顺序混乱的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 21:22:36