线程等待(wait)和通知(notify/notifyAll)是多线程编程中实现线程间协作的核心机制,简单来说就是让一个线程等待某个条件满足,另一个线程满足条件后通知等待的线程继续执行。

wait() 函数

当线程调用共享对象的wait()方法时,该线程会触发一系列原子性核心行为:

  1. 立即释放当前持有的该共享对象的监视器锁(Monitor Lock);
  2. 放弃 CPU 执行权,从 RUNNABLE 状态转为 WAITING 状态,进入该对象专属的等待队列(Wait Set) 挂起;
  3. 线程会持续挂起,直至满足以下任一条件才会尝试退出等待状态:
    (1) 其他线程调用该共享对象的notify()方法(随机唤醒等待队列中1个线程)或notifyAll()方法(唤醒等待队列中所有线程);
    (2) 其他线程调用该等待线程的interrupt()方法,此时等待线程会抛出InterruptedException异常并终止等待;

需要注意的是:无论因何种原因唤醒,线程都不会立即恢复执行,必须先重新参与该共享对象监视器锁的竞争,获取锁后才能从wait()方法的暂停处继续执行(notify()/notifyAll() 仅唤醒线程,不释放锁)。

调用前置条件

若调用 wait() 的线程未事先获取该共享对象的监视器锁,JVM 会立即抛出IllegalMonitorStateException运行时异常,如下图所示:

在这里插入图片描述

线程获取共享对象监视器锁的合法方式仅有两种:

(1) 执行以该共享对象为锁的synchronized同步代码块:

synchronized (共享变量) {
	//doSomething
}

(2) 调用该共享对象的 synchronized 实例方法(锁对象为方法所属的实例本身):

synchronized void add(int a,int b) {
	// doSomething
}

调用此方法的线程会自动获取当前实例(this)的监视器锁

核心本质:wait() 操作的是共享对象的等待队列,而等待队列属于对象监视器的一部分,只有持有监视器锁的线程,才能操作该队列。

虚假唤醒

线程调用 wait() 后进入对象的等待队列,理论上应仅在被 notify()/notifyAll() 唤醒且等待条件真实满足时继续执行,但实际场景中,线程可能在无合法唤醒信号或唤醒后等待条件已失效(如其他线程抢先处理了资源)的情况下被唤醒,这种非预期的唤醒行为称为虚假唤醒(Spurious Wakeup)。

虚假唤醒的触发场景

  • JVM / 操作系统底层机制:无外部notify()/notifyAll()调用时,操作系统或 JVM 可能因底层调度策略主动唤醒等待线程;
  • 中断唤醒:线程在 wait() 期间被 interrupt() 中断唤醒,但等待的业务条件未满足;
  • 多线程竞争:多个线程被 notifyAll() 唤醒后,部分线程抢先处理资源,导致后续线程的等待条件失效。

核心解决方案:循环检查等待条件
虚假唤醒的本质是条件不满足时被唤醒,因此必须使用while循环而非if判断等待条件;if仅做一次条件检查,无法应对唤醒后条件失效的场景,while会在线程唤醒后重新校验条件,若条件仍不满足则再次调用wait(),彻底规避虚假唤醒风险。

经典正确范式:

synchronized (obj ){
	while (条件不满足) {
		obj .wait() ;
	}
}

如上代码是经典的调用共享变量wait()方法的实例,首先通过同步块获取obj上面的监视器锁,然后在while循环内调用obj的wait() 方法。

代码示例

示例 1:基础生产者 - 消费者模型(单生产者单消费者)

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

public class CorrespondedThreadTest {

    private static BlockingQueue<String> queue = new LinkedBlockingDeque<>();

    static int MAX_SIZE = 1;

    static class Producer extends Thread {
        @Override
        public void run() {
            //生产线程
            synchronized (queue) {
                //消费队列满,则等待队列空闲
                while (queue.size() == MAX_SIZE) {
                    try {
                        //挂起当前线程,并释放通过同步块获取的queue上的锁,让消费者线程可以获取该锁,然后获取队列里面的元素
                        queue.wait();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                //空闲则生成元素,并通知消费者线程
                System.out.println("生产");
                queue.add("元素");
                queue.notify();
            }
        }
    }
    static class Consumer extends Thread {
        @Override
        public void run() {
            //生产线程
            synchronized (queue) {
                //消费队列满,则等待队列空闲
                while (queue.size() == 0) {
                    try {
                        //挂起当前线程,并释放通过同步块获取的queue上的锁,让生产者线程可以获取该锁,将生产元素放入队列
                        queue.wait();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                try {
                    queue.take();
                    System.out.println("消费");
                    ///消费元素,并通知唤醒生产者线程
                    queue.notify();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        Consumer c = new Consumer();
        Producer p = new Producer();

        c.start();
        p.start();
    }
}

在这里插入图片描述

上述代码中,queue 作为线程间共享的临界资源,生产者线程调用 queue.wait() 前,已通过 synchronized (queue) 获取该对象的监视器锁,因此调用 wait() 不会抛出 IllegalMonitorStateException 异常。

当生产者线程检测到队列无空闲容量(queue.size() == MAX_SIZE)时,会调用 queue.wait() 使自身进入 WAITING 状态;此处使用 while 循环而非 if 判断条件,核心目的是规避虚假唤醒:即便线程因 JVM 底层调度等非预期原因被唤醒,仍会重新检查队列容量,若队列仍无空闲,线程会再次调用 wait() 挂起,确保只有队列满足生产条件时才执行生产逻辑。

若生产者线程 A 通过 synchronized 获取 queue 的监视器锁后,检测到队列已满并调用 wait(),该线程会主动释放持有的 queue 监视器锁;这一行为是打破死锁的关键:若不释放锁,线程 A 会因队列满挂起且持续持有锁,而其他生产者 / 消费者线程会因无法获取锁阻塞,所有线程均无法推进,满足死锁 “持有并等待” 的必要条件,最终触发死锁。线程 A 释放锁后,其他线程可竞争并获取 queue 的监视器锁进入同步块,从而打破死锁状态。

示例 2:多线程生产者 - 消费者模型(多生产者多消费者)

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

public class CorrespondedThreadTest1 {

    private static BlockingQueue<String> queue = new LinkedBlockingDeque<>();

    static int MAX_SIZE = 1;

    static class Producer extends Thread {
        @Override
        public void run() {
            while (true){
                //生产线程
                synchronized (queue) {
                    //消费队列满,则等待队列空闲
                    while (queue.size() == MAX_SIZE) {
                        try {
                            //挂起当前线程,并释放通过同步块获取的queue上的锁,让消费者线程可以获取该锁,然后获取队列里面的元素
                            queue.wait();
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                    //空闲则生成元素,并通知消费者线程
                    queue.add("元素");
                    System.out.println("生产");
                    queue.notifyAll();
                }
            }

        }
    }
    static class Consumer extends Thread {

        @Override
        public void run() {
            while(true){
                //生产线程
                synchronized (queue) {
                    //消费队列满,则等待队列空闲
                    while (queue.size() == 0) {
                        try {
                            //挂起当前线程,并释放通过同步块获取的queue.上的锁,让生产者线程可以获取该锁,将生
                            //产元素放入队列
                            queue.wait();
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                    try {
                        queue.take();
                        System.out.println("消费");
                        ///消费元素,并通知唤醒生产者线程
                        queue.notifyAll();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }

        }
    }
    
    public static void main(String[] args) throws InterruptedException {

        Consumer c = new Consumer();
        Producer p = new Producer();

       /* Consumer c1 = new Consumer();
        Producer p1 = new Producer();*/
        Thread.sleep(1000);
        c.start();
        p.start();
        /*c1.start();
        p1.start();*/
    }
}

在这里插入图片描述

    public static void main(String[] args) throws InterruptedException {

        Consumer c = new Consumer();
        Producer p = new Producer();

        Consumer c1 = new Consumer();
        Producer p1 = new Producer();
        Thread.sleep(1000);
        c.start();
        p.start();
        c1.start();
        p1.start();
    }

在这里插入图片描述

生产者线程获取 queue 监视器锁后,若检测到队列已满则调用 wait() 释放锁并挂起,避免死锁;while 循环仍用于规避虚假唤醒。区别在于多线程场景需将 notify() 替换为 notifyAll():notify() 仅随机唤醒等待队列中的一个线程,可能出现生产者唤醒生产者、消费者唤醒消费者的无效唤醒,导致部分线程永久等待;而 notifyAll() 会唤醒所有等待在 queue 上的线程,线程竞争锁后通过 while 循环校验条件,确保只有符合生产 / 消费条件的线程执行核心逻辑,避免线程饥饿。

wait () 被中断唤醒的场景

public class WaitNotifyInterupt {
    static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        //创建线程
        Thread threadA = new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println("---begin---");
                    //阻塞当前线程
                    synchronized (obj) {
                        obj.wait();
                    }
                    System.out.println("---end---");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });

        threadA.start();
        Thread.sleep(1000);
        System.out.println("---begin interrupt threadA---");
        threadA.interrupt();
        System.out.println("---end interrupt threadA---");

        boolean interrupted = threadA.isInterrupted();
        System.out.println(interrupted);

    }
}

在这里插入图片描述

线程调用共享对象的 wait() 方法进入 WAITING 状态后,若其他线程调用该线程的 interrupt() 方法,会触发中断唤醒:threadA 在 obj.wait() 处立即抛出 InterruptedException 异常,终止等待状态并返回;抛出异常后,线程的中断标记会被自动清除(因此示例中 threadA.isInterrupted() 输出 false)。需注意:中断唤醒仅终止线程的等待状态,不会改变共享对象的监视器锁状态,也不保证线程等待的业务条件=成立,因此中断唤醒后仍需通过循环校验条件,避免逻辑错误。

wait (long timeout)

wait(long timeout) 是java.lang.Object类中无参wait()方法的超时重载版本,其核心协作机制(基于对象监视器锁的释放、挂起、唤醒后锁竞争)与无参wait()完全一致,核心差异仅在于新增超时自动唤醒机制。

关键规则如下:

  • 超时参数规则:
    timeout 参数的单位为毫秒(ms),表示线程最长等待时长;
    若 timeout = 0 等价于无参wait()方法,这也是无参wait()的底层实现逻辑(wait()内部直接调用wait(0));
    若传递负数的timeout值 JVM 会立即抛出IllegalArgumentException运行时异常(参数合法性校验失败)。
  • 返回条件:
    条件 1(继承自 wait ()):其他线程调用该共享对象的notify()(随机唤醒 1 个等待线程)或notifyAll()(唤醒所有等待线程);
    条件 2(继承自 wait ()):其他线程调用该等待线程的interrupt()方法,线程抛出InterruptedException异常并退出等待;
    条件 3(新增超时条件):等待时长超过timeout且未被上述两类条件唤醒,线程主动退出等待状态(无异常抛出)。
  • 唤醒后执行逻辑:
    线程需先重新参与共享对象监视器锁的竞争,若锁被其他线程持有,会阻塞至获取锁;
    获取锁后, 从wait(long timeout) 方法的暂停处继续执行;
    超时唤醒仅代表等待时长到期,不代表线程等待的业务条件已满足,因此仍需通过while循环校验条件,规避虚假唤醒风险。
  • 异常规则
    仅当线程在timeout时长内被interrupt()中断时,才会在wait(long timeout)处抛出InterruptedException;
    线程因中断抛出异常后,中断标记会被自动清除,若需保留中断语义,需在 catch 块中手动调用Thread.currentThread().interrupt()恢复标记。

notify() 函数

notify() 是 java.lang.Object 类的原生方法,是基于对象监视器(Monitor)实现的线程间协作机制核心方法之一,其核心作用是唤醒共享对象等待队列中随机一个因调用 wait 系列方法(wait()/wait(long))而进入 WAITING 状态的线程,是实现线程间 “条件满足后通知协作” 的关键手段。

notify () 的唤醒规则:
当线程调用共享对象的notify()方法时,JVM 会从该对象的等待队列中,随机选择一个调用过该对象 wait 系列方法的等待线程进行唤醒;
等待队列中若有多个线程,开发者无法控制具体唤醒哪一个,完全由 JVM 底层调度机制决定;
若该对象的等待队列为空(无线程等待),调用notify()不会产生任何效果,也不会抛出异常。

被 notify() 唤醒的线程的执行逻辑:
状态转换:从 WAITING(等待状态)转为 BLOCKED(阻塞状态),进入该对象的锁竞争队列;
锁竞争:通知线程释放锁后,被唤醒的线程需要与其他正在竞争该锁的线程共同竞争监视器锁;
恢复执行:只有竞争到锁的线程,才能从调用wait()的位置继续执行;若竞争失败,会持续处于 BLOCKED 状态,直到再次抢到锁。

调用前置条件

调用前置条件与 wait 系列方法完全一致,notify() 调用线程必须先获取该共享对象的监视器锁(内置锁),否则 JVM 会立即抛出 IllegalMonitorStateException 运行时异常。

在这里插入图片描述

合法调用场景仅两种(与 wait () 一致):
(1) 在以该共享对象为锁的synchronized同步代码块内调用:

synchronized (共享对象) {
    共享对象.notify(); // 合法:当前线程持有共享对象的锁
}

(2) 在该共享对象的synchronized实例方法内调用(锁为this):

synchronized void notifyThread() {
    this.notify(); // 等价于notify(),合法
}

核心本质:notify()操作的是共享对象的等待队列,而等待队列属于对象监视器的一部分,只有持有监视器锁的线程,才能操作该队列。

需要注意的是:调用 notify() 的线程,在执行 notify() 方法后,不会立即释放所持有的共享对象监视器锁;监视器锁仍由调用 notify() 的线程持有,直到调用 notify() 的线程退出 synchronized 同步代码块 / 同步方法,锁才会被释放;
示例逻辑:

synchronized (queue) {
    queue.notify(); // 调用notify(),但锁仍由当前线程持有
    System.out.println("执行notify后,锁仍未释放"); // 这行代码执行完,退出同步块,锁才释放
}

代码示例

public class Basket {

    private boolean isEmpty;

    public boolean isEmpty() {
        return isEmpty;
    }

    public void setEmpty(boolean empty) {
        isEmpty = empty;
    }
}
public class Producer extends Thread {
    
    private Basket basket;

    public Producer(Basket basket){
        super();
        this.basket = basket;
    }

    @Override
    public void run() {

        while(true){
            //定义一个同步代码块
            synchronized (basket){
                try {
                    if(!basket.isEmpty()){
                        //线程等待的状态
                        basket.wait();
                    }
                    System.out.println("生成水果");
                    basket.setEmpty(false);
                    //通知在这个共享对象上等待的线程
                    basket.notify();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {


            }

        }
    }
}
public class Consumer extends Thread{

    private Basket basket;

    public Consumer(Basket basket){
        super();
        this.basket = basket;
    }

    @Override
    public void run() {

        while(true){
            //定义一个同步代码块
            synchronized (basket){
                try {
                    if(basket.isEmpty()){
                        //线程等待的状态
                        basket.wait();
                    }
                    System.out.println("消费水果");
                    basket.setEmpty(true);
                    //通知在这个共享对象上等待的线程
                    basket.notify();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }

        }
    }

}
public class Test {
    public static void main(String[] args) {
        Basket basket = new Basket();
        Producer producer = new Producer(basket);
        Consumer consumer = new Consumer(basket);

        Thread t = new Thread(producer);
        Thread t1 = new Thread(consumer);

        t.start();
        t1.start();
    }
}

在这里插入图片描述

如上代码的单生产者 - 单消费者场景下 notify() 可安全使用;
但在多生产者 - 多消费者场景下,若使用 notify(),可能出现无效唤醒:
生产者线程调用notify(),随机唤醒了另一个生产者线程(而非消费者线程);被唤醒的生产者线程竞争到锁后,检查队列仍满,会再次调用 wait() 挂起,而真正需要被唤醒的消费者线程永远无法被唤醒,导致线程饥饿;优先使用 notifyAll(),结合while循环校验条件,避免无效唤醒和线程饥饿。

notifyAll()

notifyAll() 是 java.lang.Object 类的原生方法,是基于对象监视器(Monitor)实现的线程协作核心方法,其核心作用是:唤醒共享对象等待队列中所有因调用 wait 系列方法(wait()/wait(long))而进入 WAITING 状态的线程,是解决多线程场景下notify() 随机唤醒导致线程饥饿问题的核心方案。

唤醒规则:
调用 notifyAll() 后,JVM 会将该共享对象等待队列中的所有等待线程全部移出。
若共享对象的等待队列为空,调用notifyAll()不会产生任何效果,也不会抛出异常。

被唤醒线程的执行逻辑:
所有被唤醒的线程从WAITING状态转为BLOCKED状态,进入该对象的锁竞争队列;
调用notifyAll()的线程退出同步块、释放监视器锁后,这些被唤醒的线程会与其他竞争该锁的线程共同竞争锁;
只有抢到锁的线程,才能从wait()方法的暂停处继续执行;执行完毕释放锁后,下一个抢到锁的线程再执行,直至所有被唤醒线程全部完成执行。

调用前置条件

只有持有该共享对象监视器锁的线程才能调用,否则 JVM 会抛出IllegalMonitorStateException运行时异常;且调用 notifyAll() 时不会立即释放锁,锁需等调用线程退出synchronized同步块 / 方法后才释放。

不同于在共享变量上调用notify()函数会唤醒被阻塞到该共享变量上的一个线程,notifyAll() 方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。

代码示例

public class NotifyAllTest {
    //创建资源
    private static volatile Object resourceA = new Object();

    public static void main(String[] args) throws InterruptedException {
        //创建线程
        Thread threadA = new Thread(new Runnable() {
            public void run() {
                //获取resourceA共享资源的监视器锁
                synchronized (resourceA) {
                    System.out.println("threadA get resourceA lock");
                    try {
                        System.out.println("threadA begin wait");
                        resourceA.wait();
                        System.out.println("threadA end wait");

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        //创建线程
        Thread threadB = new Thread(new Runnable() {
            public void run() {
                synchronized (resourceA) {
                    System.out.println("threadB get resourceA lock");
                    try {
                        System.out.println("threadB begin wait");
                        resourceA.wait();
                        System.out.println("threadB end wait");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });


        //创建线程
        Thread threadC = new Thread(new Runnable() {
            public void run() {
                synchronized (resourceA) {
                    System.out.println("threadC begin notify");
                    resourceA.notify();
                    //resourceA.notifyAll();
                }
            }
        });
        //启动线程
        threadA.start();

        threadB.start() ;
        Thread. sleep (1000) ;
        threadC.start() ;
        //等待线程结束
        threadA. join() ;
        threadB. join() ;
        threadC.join() ;
        System.out.println ("main over") ;
    }
}

如上代码开启了三个线程,其中线程A和线程B分别调用了共享资源resourceA的wait()方法,线程C则调用了nofity() 方法。这里启动线程C前首先调用sleep 方法让主线程休眠1s,这样做的目的是让线程A和线程B全部执行到调用wait方法后再调用线程C的notify方法。这个例子试图在线程A和线程B都因调用共享资源resourceA的wait()方法而被阻塞后,让线程C再调用resourceA的notify()方法,从而唤醒线程A和线程B。但是从执行结果来看,只有一个线程A被唤醒,线程B没有被唤醒。

在这里插入图片描述

从输出结果可知线程调度器这次先调度了线程A占用CPU来运行,线程A首先获取resourceA上面的锁,然后调用resourceA的wait()方法挂起当前线程并释放获取到的锁,然后线程B获取到resourceA上的锁并调用resourceA的wait()方法,此时线程B也被阻塞挂起并释放了resourceA. 上的锁,到这里线程A和线程B都被放到了resourceA 的阻塞集合里面。线程C休眠结束后在共享资源resourceA上调用了notify() 方法,这会激活resourceA的阻塞集合里面的一个线程,这里激活了线程A,所以线程A调用的wait()方法返回了,线程A执行完毕。而线程B还处于阻塞状态。如果把线程C调用的notify()方法改为调用notifyAll()方法,则执行结果如下。

在这里插入图片描述

从输入结果可知线程A和线程B被挂起后,线程C调用notifyAll()方法会唤醒resourceA的等待集合里面的所有线程,这里线程A和线程B都会被唤醒,只是线程B先获取到resourceA.上的锁,然后从wait()方法返回。线程B执行完毕后,线程A又获取了resourceA上的锁,然后从wait()方法返回。线程A执行完毕后,主线程返回,然后打印输出。

LockSupport

LockSupport 是 java.util.concurrent.locks 包下的基础工具类,JDK 官方定义为创建锁和其他同步类的线程阻塞原语—— 它是 JUC 中几乎所有同步组件的底层实现基石,核心作用是通过 “许可” 机制实现线程的精准阻塞与唤醒,功能上替代了传统的 wait/notify,灵活性和易用性远超后者。

wait()、notify()(含notifyAll())是基于 Object 监视器实现的传统线程协作方法,核心缺点如下:

  • 强锁依赖
    wait/notify 操作的是对象监视器的等待队列,只有持有监视器锁的线程才拥有操作该队列的权限;
    该限制大幅压缩了方法的使用场景,无法在无锁的业务逻辑中实现线程阻塞 / 唤醒,灵活性极低。
  • 唤醒粒度不可控
    当共享对象的等待队列中有多个线程时,notify() JVM 会随机选择一个等待线程唤醒,开发者无法干预唤醒目标,若唤醒的是无需执行的线程,会导致无效唤醒,甚至引发线程饥饿;
    notifyAll() 虽能唤醒所有等待线程,但所有被唤醒线程会竞争同一把监视器锁,仅能串行执行,且仍无法定向唤醒特定线程;

在这里插入图片描述

  • LockSupport.park() 是 LockSupport 的核心阻塞方法,其核心功能是:阻塞当前执行线程,直至该线程获得许可、被中断或 JVM 内部原因唤醒。
  • park(Object blocker) 是 park() 的增强版本,核心功能与 park() 完全一致,唯一区别是新增 blocker 参数(阻塞关联对象),该参数的核心价值是辅助问题排查与监控。
  • LockSupport.unpark(Thread thread) 是唤醒线程的核心方法,其核心功能是:为指定线程发放许可(Permit),若该线程正处于 park 阻塞状态,则立即唤醒;若未阻塞,许可会被保留。

代码示例:

import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {

    public static void main(String[] args) throws InterruptedException {
        Thread parkThread = new Thread(new ParkThread());
        parkThread.start();
        Thread.sleep(3000);
        LockSupport.unpark(parkThread);

    }

    static class ParkThread implements Runnable{
        @Override
        public void run() {
            System.out.println("开始线程阻塞");
            //挂起当前的线程
            LockSupport.park();
            System.out.println("结束线程阻塞");
        }
    }
}

在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐