Java多线程wait()和notify()方法详细图解

作者:Killing?Vibe 时间:2021-09-19 20:27:32 

一、线程间等待与唤醒机制

wait()和notify()是Object类的方法,用于线程的等待与唤醒,必须搭配synchronized 锁来使用。

多线程并发的场景下,有时需要某些线程先执行,这些线程执行结束后其他线程再继续执行。

比如: 一个长跑比赛,裁判员要等跑步运动员冲线了才能宣判比赛结束,那裁判员线程就得等待所有的运动员线程运行结束后,再唤醒这个裁判线程。

二、等待方法wait()

wait 做的事情:

  • 使当前执行代码的线程进行等待. (把线程放到等待队列中)

  • 释放当前的锁

  • 满足一定条件时被唤醒, 重新尝试获取这个锁.

wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.

Java多线程wait()和notify()方法详细图解

wait 结束等待的条件:

  • 其他线程调用该对象的 notify 方法.

  • wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).

  • 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

注意事项:

  1. 调用wait()方法的前提是首先要获取该对象的锁(synchronize对象锁)

  2. 调用wait()方法会释放锁,本线程进入等待队列等待被唤醒,被唤醒后不是立即恢复执行,而是进入阻塞队列竞争锁

等待方法:

1.痴汉方法,死等,线程进入阻塞态(WAITING)直到有其他线程调用notify方法唤醒

Java多线程wait()和notify()方法详细图解

2.等待一段时间,若在该时间内线程被唤醒,则继续执行,若超过相应时间还没有其他线程唤醒此线程,此线程不再等待,恢复执行。

Java多线程wait()和notify()方法详细图解

调用wait方法之后:

Java多线程wait()和notify()方法详细图解

三、唤醒方法notify()

notify 方法是唤醒等待的线程.

  • 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。

  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)

  • 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

注意事项:

  • notify():随机唤醒一个处在等待状态的线程。

  • notifyAll():唤醒所有处在等待状态的线程。

  • 无论是wait还是notify方法,都需要搭配synchronized锁来使用(等待和唤醒,也是需要对象)

Java多线程wait()和notify()方法详细图解

四、关于wait和notify内部等待问题(重要)

对于wait和notify方法,其实有一个阻塞队列也有一个等待队列

  • 阻塞队列表示同一时间只有一个线程能获取到锁,其他线程进入阻塞队列

  • 等待队列表示调用wait (首先此线程要获取到锁,进入等待队列,释放锁

举个栗子:

现有如下定义的等待线程任务

private static class WaitTask implements Runnable {
       private Object lock;
       public WaitTask(Object lock) {
           this.lock = lock;
       }
       @Override
       public void run() {
           synchronized (lock) {
               System.out.println(Thread.currentThread().getName() + "准备进入等待状态");
               // 此线程在等待lock对象的notify方法唤醒
               try {
                   lock.wait();
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               System.out.println(Thread.currentThread().getName() + "等待结束,本线程继续执行");
           }
       }
   }

然后创建三个等待线程:

Java多线程wait()和notify()方法详细图解

由于同一时间只有一个线程(随机调度)能获取到synchronized锁,所以会有两个线程没竞争到锁,从而进入了阻塞队列

这里假如t2先竞争到了锁,所以先会阻塞t1和t3:

Java多线程wait()和notify()方法详细图解

又由于调用wait方法会释放锁,调用wait方法的线程t2就会进入等待队列,直到被notify唤醒或者超时自动唤醒。

Java多线程wait()和notify()方法详细图解

然后此时lock对象已经被释放了,所以t1和t3 又可以去竞争这个锁了,就从阻塞队列里面竞争锁。

这里假如t3 竞争到了锁,阻塞队列只剩下t1:

Java多线程wait()和notify()方法详细图解

然后t3运行到了wait方法,释放锁,然后进入等待队列

Java多线程wait()和notify()方法详细图解

然后重复这些操作~~,最后t1,t2,t3 都进入了等待队列中,等待notify线程唤醒(这里假设notify要放在这些线程start后的好几秒后,因为notify线程也是和这些线程并发执行的,所以等待队列中的线程随时可能被唤醒

Java多线程wait()和notify()方法详细图解

重点来了:

在等待队列中的线程,被notify唤醒之后,会直接回到阻塞队列去竞争锁!!!而不是直接唤醒~

举个栗子:

拿notifyAll()来举例,假如此时等待队列中有三个线程t1,t2,t3,那么调用notifyAll()会直接把它们三个直接从等待队列中进入到阻塞队列中:

Java多线程wait()和notify()方法详细图解

然后再去竞争这个锁,去执行wait之后的代码~~

五、完整代码(仅供测试用)

private static class WaitTask implements Runnable {
       private Object lock;
       public WaitTask(Object lock) {
           this.lock = lock;
       }
       @Override
       public void run() {
           synchronized (lock) {
               System.out.println(Thread.currentThread().getName() + "准备进入等待状态");
               // 此线程在等待lock对象的notify方法唤醒
               try {
                   lock.wait();
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               System.out.println(Thread.currentThread().getName() + "等待结束,本线程继续执行");
           }
       }
   }
   private static class NotifyTask implements Runnable {
       private Object lock;
       public NotifyTask(Object lock) {
           this.lock = lock;
       }
       @Override
       public void run() {
           synchronized (lock) {
               System.out.println("准备唤醒");
               // 唤醒所有线程(随机)
               lock.notifyAll();
               System.out.println("唤醒结束");
           }
       }
   }

public static void main(String[] args) throws InterruptedException {
       Object lock = new Object();
       Object lock2 = new Object();
       // 创建三个等待线程
       Thread t1 = new Thread(new WaitTask(lock),"t1");
       Thread t2 = new Thread(new WaitTask(lock),"t2");
       Thread t3 = new Thread(new WaitTask(lock),"t3");
      // 创建一个唤醒线程
       Thread notify = new Thread(new NotifyTask(lock2),"notify线程");
       t1.start();
       t2.start();
       t3.start();
       ;
       Thread.sleep(100);
       notify.start();
       // 当前正在执行的线程数
       Thread.sleep(2000);
       System.out.println(Thread.activeCount() - 1);
   }

六、wait和sleep方法的区别(面试题):

  • wait方法是Object类提供的方法,需要搭配synchronized锁来使用,调用wait方法会释放锁,线程进入WAITING状态,等待被其他线程唤醒或者超时自动唤醒,唤醒之后的线程需要再次竞争synchronized锁才能继续执行。

  • sleep方法是Thread类提供的方法,调用sleep方法的线程进入TIMED_WAITING状态,不会释放锁,时间到自动唤醒。

来源:https://blog.csdn.net/qq_43575801/article/details/127601039

标签:多线程,wait(),notify()
0
投稿

猜你喜欢

  • 总结Android App内存优化之图片优化

    2022-07-01 22:52:16
  • Android实现登录界面的注册功能

    2021-10-22 01:17:55
  • Java System类用法实战案例

    2023-04-11 07:28:59
  • volatile与happens-before的关系与内存一致性错误

    2021-12-13 20:25:37
  • Java BIO,NIO,AIO总结

    2022-02-07 01:48:28
  • Android 跨进程通Messenger(简单易懂)

    2022-03-24 00:48:25
  • java使用FFmpeg合成视频和音频并获取视频中的音频等操作(实例代码详解)

    2023-10-28 15:42:24
  • SpringBoot之使用Redis实现分布式锁(秒杀系统)

    2022-09-30 15:45:08
  • Android自定义SeekBar实现滑动验证且不可点击

    2023-11-08 04:35:45
  • C#调用新浪微博API实例代码

    2022-12-21 14:02:15
  • 利用C#版OpenCV实现圆心求取实例代码

    2022-10-28 12:51:48
  • Android开发之机顶盒上gridview和ScrollView的使用详解

    2021-08-06 11:57:59
  • Java 从互联网上爬邮箱代码示例

    2022-02-27 16:40:57
  • Java如何使用HTTPclient访问url获得数据

    2023-11-12 04:16:21
  • Android UI之ImageView实现图片旋转和缩放

    2023-08-04 02:53:39
  • springboot用thymeleaf模板的paginate分页完整代码

    2023-10-21 05:06:05
  • Java获取控制台输入的两种方法小结

    2023-11-29 12:40:44
  • Unity Shader实现新手引导遮罩镂空效果

    2022-08-18 16:25:12
  • Java5种遍历HashMap数据的写法

    2022-09-27 15:43:27
  • mvc开启gzip压缩示例分享

    2022-05-03 08:34:29
  • asp之家 软件编程 m.aspxhome.com