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,进入等待状态,如下图所示:
notify
同样,在执行 notify() 方法前,当前线程也必须已获得线程锁。
调用 notify() 方法后,会通知一个执行了 wait() 方法的阻塞等待线程,使该等待线程重新获取到对象锁,然后继续执行 wait() 后面的代码。
但是,与 wait() 方法不同,执行 notify() 后,不会立即释放对象锁,而需要执行完 synchronize 的代码块或方法才会释放锁,所以接收通知的线程也不会立即获得锁,也需要等待执行 notify() 方法的线程释放锁后再获取锁。
下面是 notify() 方法的使用,实现一个完整的 wait/notify 的例子,代码如下:
以上 A 线程执行 wait() 方法,B 线程执行 notify() 方法,执行结果为:
执行结果中可以看到,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年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》