wait、notify、notifyAll方法(区别与使用详解)

wait、notify、notifyAll方法(区别与使用详解)-mikechen

wait、notify、notifyAll这三个方是Java多线程提供的实现线程间阻塞和控制进程内调度的底层方法@mikechen

实现 wait/notify 机制的条件

调用 wait 线程和 notify 线程必须拥有相同对象锁。

wait() 方法和 notify()/notifyAll() 方法必须在 Synchronized 方法或代码块中。

由于 wait/notify 方法是定义在 java.lang.Object中,所以在任何 Java 对象上都可以使用。

 

wait

wait()方法自动释放占有的对象锁,并等待notify,前提是必须先获得锁,一般配合synchronized 关键字使用,如下代码示例:

public synchronized void methodName() {
    while(contidion) {
        object.wait();
    }
}

当线程执行wait()方法时候会释放当前的锁,然后让出CPU,进入等待状态,如下图所示:

wait、notify、notifyAll方法(区别与使用详解)-mikechen

 

notify

同样,在执行 notify() 方法前,当前线程也必须已获得线程锁。

调用 notify() 方法后,会通知一个执行了 wait() 方法的阻塞等待线程,使该等待线程重新获取到对象锁,然后继续执行 wait() 后面的代码。

但是,与 wait() 方法不同,执行 notify() 后,不会立即释放对象锁,而需要执行完 synchronize 的代码块或方法才会释放锁,所以接收通知的线程也不会立即获得锁,也需要等待执行 notify() 方法的线程释放锁后再获取锁。

下面是 notify() 方法的使用,实现一个完整的 wait/notify 的例子,代码如下:

wait、notify、notifyAll方法(区别与使用详解)-mikechen

以上 A 线程执行 wait() 方法,B 线程执行 notify() 方法,执行结果为:

wait、notify、notifyAll方法(区别与使用详解)-mikechen

执行结果中可以看到,B 线程执行 notify() 方法后,即使 sleep 了,A 线程也没有获取到锁,可知,notify() 方法并没有释放锁。

notify() 是通知到等待中的线程,但是调用一次 notify() 方法,只能通知到一个执行 wait() 方法的等待线程。如果有多个等待状态的线程,则需多次调用 notify() 方法,通知到线程顺序则根据执行 wait() 方法的先后顺序进行通知。

 

notifyAll

notifyAll() 方法跟 notify() 方法一样,区别在于 notifyAll() 方法唤醒在此对象监视器上等待的所有线程,notify() 方法是一个线程。

代码示例如下:

public class ThreadWaitAndNotifyAll implements Runnable {

    // 模拟多个线程的共享变量
    private static Object object = new Object();

    @Override
    public void run() {
        synchronized (object) {
            System.out.println("线程" + Thread.currentThread().getName() + "获得锁,进入等待状态");
            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程" + Thread.currentThread().getName() + "执行最后部分结束");
        }
    }

    public static void main(String[] args) {
        ThreadWaitAndNotifyAll runnable = new ThreadWaitAndNotifyAll();
        Thread thread0 = new Thread(runnable);
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println("线程" + Thread.currentThread().getName() + "获得锁,开始通知唤醒所有线程");
                    // 唤醒其他线程
                    object.notifyAll();
                    // 调用完notifyAll()方法后,同步代码块中的其他代码,必须执行完后才能将对象锁释放,而不是调用了notifyAll()方法后立即释放。
                    System.out.println("线程" + Thread.currentThread().getName() + "执行结束");
                }
            }
        });
        // 每次运行,线程0和线程1的顺序可能会不同,执行顺序由CPU决定
        thread0.start();
        thread1.start();
        try {
            // 加一个延时,让线程2一定在线程0和1之后执行,否则线程2中的notifyAll方法将无效
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }
}

执行结果如下:

线程Thread-0获得锁,进入等待状态
线程Thread-1获得锁,进入等待状态
线程Thread-2获得锁,开始通知唤醒所有线程
线程Thread-2执行结束
线程Thread-1执行最后部分结束
线程Thread-0执行最后部分结束

注意: 调用完notifyAll()方法后,同步代码块中的其他代码,必须执行完后才能将对象锁释放,而不是调用了notifyAll()方法后立即释放。

 

wait、notify、notifyAll的区别

  • wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。
  • 一般在synchronized同步代码块里使用wait()、notify()和notifyAll()方法。
  • 当线程执行wait()方法时,会释放当前的锁,然后让出CPU,进入等待队列,所以使用wait()的前提的先获得锁。
  • wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒。
  • notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。
  • 永远在循环里调用wait()和notify(),而不是在if语句。
  • notify方法只唤醒一个等待线程,notifyAll 会唤醒所有等待线程,尽管哪一个线程将会第一个处理取决于操作系统。

作者简介

陈睿|mikechen,10年+大厂架构经验,BAT资深面试官,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

👇阅读更多mikechen架构文章👇

阿里架构 |双11秒杀 |分布式架构 |负载均衡 |单点登录 |微服务 |云原生 |高并发 |架构师

以上

关注作者「mikechen」公众号,获取更多技术干货!

后台回复架构,即可获取《阿里架构师进阶专题全部合集》,后台回复面试即可获取《史上最全阿里Java面试题总结

评论交流
    说说你的看法