java中wait,notify,notifyAll,sleep方法的作用和区别

本文解析了Java中Object类的wait(), notify(), notifyAll()在多线程同步中的作用,强调了配合synchronized的关键字使用以及它们在对象锁和线程状态转换中的核心原理。

在进行多线程编程时,进程会遇到多线程之前的同步已经等待场景。
在java中,Object类提供了wait、nofity、notifyAll用来进行线程间的同步。一般如果我们调用wait、nofity、notifyAll都是结合synchronized同步关键字来使用:

 new Thread(()->{
            synchronized (monitor){

                try {
                    System.out.println("1. enter monitor ");
                    Thread.sleep(5000);
                    System.out.println("1. call wait ");
                    monitor.wait();
                    System.out.println("1. get monitor again ");
                    Thread.sleep(5000);
                    System.out.println("1. call notify ");
                    monitor.notify();
                    System.out.println("1. finished");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(()->{
            synchronized (monitor){
                try {
                    System.out.println("2. enter monitor ");
                    Thread.sleep(5000);
                    System.out.println("2. call notify ");
                    monitor.notify();
                    System.out.println("2. get monitor again ");
                    Thread.sleep(5000);
                    System.out.println("2. call monitor ");
                    monitor.wait();
                    System.out.println("2. finished ");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

打印:

1. enter monitor 
1. call wait 
2. enter monitor 
2. call notify 
2. get monitor again 
2. call monitor 
1. get monitor again 
1. call notify 
1. finished
2. finished 

在java中每个对象都有一个对象锁,里面会维护一个EntryList 中保存目前等待获取锁的线程,WaitSet 保存 wait 的线程。我们调用wait方法时,已经获得了对象锁的线程会让出锁进行阻塞等待,将当前线程放入到waitSet ,而调用notify之后,当前线程也会让出锁,然后从对象锁的waitSet中去一个线程进行唤醒。

这里有几个点需要注意,

  1. 调用wait方法的调用方必须是synchronized锁的持有者,如果是synchronized代码块,那么这个持有者就是同步的对象实例,如果是在类方法,那么就是这个类实例,如果在静态方法那么就是这个类本身
  2. 调用wait方法前,必须获取synchronized锁,也就是调用wait方法必须在synchronized的作用范围内,如果在其他地方调用,则会报如下错误:
Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.gridsum.ad.agg.query.platform.util.Utils.main(Utils.java:44)
  1. 调用wait方法后,线程进入阻塞等待,必须等待其他线程调用notify/notifyAll来进行唤醒

wait方法会释放当前已经获得的synchronized锁,直到其他线程调用notify/notifyAll唤醒重新竞争获取锁。

而Thread.sleep只是让线程进行休眠,不会释放任何锁资源。
而像Thread.sleep(0) 有点类似 Thread.yield(),表示出让当前CPU时间片,重新获取。

### waitnotifynotifyAll 方法 #### wait 方法 wait 方法是 Object 类中的方法,JDK 中的每一个类都拥有这三个重载方法。 - `public final void wait()`:导致当前线程等待,直到另一个线程调用该对象的 `notify()` 方法或 `notifyAll()` 方法。该方法等价于 `wait(0)`,0 代表永不超时。当前线程必须拥有该对象的监视器,执行此方法后,线程释放此监视器的所有权,并进入与该对象关联的 wait set 中,等待其他线程通过调用 `notify` 或 `notifyAll` 方法通知等待该对象监视器的线程,然后线程等待重新获得监视器的所有权后恢复执行[^1]。 - `public final void wait(long timeout)`:导致当前线程等待,直到另一个线程调用此对象的 `notify()` 方法或 `notifyAll()` 方法,或指定的时间已过。当前线程执行该方法后会放弃对该监视器的所有权并进入等待状态,若超时时间到达会自动唤醒[^1]。 - `public final void wait(long timeout, int nanos)`:导致当前线程等待,直到另一个线程调用此对象的 `notify()` 方法或 `notifyAll()` 方法,或其他一些线程中断当前线程,或一定量的实时时间。这三个 `wait` 方法最终都会调用 `wait(long timeout)` 方法[^1]。 当一个线程调用一个共享变量的 `wait()` 方法时,该调用线程会被阻塞挂起,直到发生以下事情之一才返回: - 线程调用了该共享对象的 `notify()` 或者 `notifyAll()` 方法。 - 其他线程调用了该线程的 `interrupt()` 方法,此时该线程会抛出 `InterruptedException` 异常返回[^2]。 示例代码如下: ```java class SharedObject { public synchronized void waitMethod() throws InterruptedException { wait(); } } ``` #### notify 方法 `notify()` 方法用于唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,则会随机选择一个线程进行唤醒。被唤醒的线程需要等待调用 `notify` 方法的线程释放监视器后,才有机会重新获得监视器的所有权并继续执行。 示例代码如下: ```java class SharedObject { public synchronized void notifyMethod() { notify(); } } ``` #### notifyAll 方法 `notifyAll()` 方法用于唤醒在此对象监视器上等待的所有线程。所有被唤醒的线程会竞争该对象的监视器所有权,获得所有权的线程才能继续执行。 示例代码如下: ```java class SharedObject { public synchronized void notifyAllMethod() { notifyAll(); } } ``` ### 并发(concurrent) 在 Java 中,并发编程是指多个线程同时执行的编程方式。`wait`、`notify` `notifyAll` 方法Java 中实现线程间协作同步的重要手段,用于解决多线程之间的通信问题。通过这些方法,线程可以在共享资源上进行等待唤醒操作,从而实现线程间的协调工作,避免数据竞争不一致的问题。 ### 中断(interrupt) 在 Java 中,线程的中断是一种协作机制。`interrupt()` 方法用于中断线程。当一个线程调用另一个线程的 `interrupt()` 方法时,会为被调用线程设置一个中断标志。 - 如果线程在调用 `wait`、`join` 或 `sleep` 方法时被中断,会抛出 `InterruptedException` 异常,并且会清除中断标志。 - 如果线程在正常运行时被中断,中断标志会被设置,线程可以通过 `isInterrupted()` 方法检查中断标志来决定是否退出。 示例代码如下: ```java class InterruptExample implements Runnable { @Override public void run() { try { while (!Thread.currentThread().isInterrupted()) { System.out.println("Running..."); Thread.sleep(1000); } } catch (InterruptedException e) { System.out.println("Thread interrupted"); } } } public class Main { public static void main(String[] args) { Thread thread = new Thread(new InterruptExample()); thread.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); } } ``` ### 相关问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值