理解wait和notify

妖狐艹你老母 2024-02-18 12:43 104阅读 0赞

文章目录

  • 前言
  • 一、场景描述
  • 二、wait和notify
    • 1.wait
      • 1.wait进行阻塞
      • 2.wait的使用
    • 2.notify
      • 1.notify唤醒线程
      • 2.notify的使用
      • 3.notifyAll()的使用
  • 总结

前言

线程最大的问题是抢占式执行,随机调度,但是随机性的东西,往往是最难掌握的,为了解决这个问题,从而会使用一些API让线程主动阻塞,主动放弃CPU,而wait和notify就是其中之一。


一、场景描述

下面出现一个场景:t1和t2两个线程,希望t1先来执行,当t1的线程差不多执行到一半时,就让t1等待,通知t2开始执行,等t2完成执行后,再通知t1继续执行。

上面的场景使用sleep和join行不行?

1.使用sleep,需要指定一个具体休眠时间,在上面的场景中,我们并不知道t1执行需要多少时间,所以使用sleep是不行的;
2.使用join,需要t1彻底执行完成后,才可以让t2开始执行,不满足上述场景。

我们可以使用wait和notify来解决上述场景

二、wait和notify

1.wait

1.wait进行阻塞

某个线程调用wait方法,就会进入阻塞状态,进入WAITING

2.wait的使用

1.throws InterruptedException: 这个异常,大多数带有阻塞功能的方法都带有,这些方法都可以被interrupt方法通过这个异常给唤醒

2.object.wait():wait不参加任何参数,即为死等,一直等待其他线程唤醒它

在这里插入图片描述

注意: 上面的代码是会报错的

在这里插入图片描述

1.IllegalMonitorStateException:非法的锁状态异常,通常情况下,锁的状态只有两种,加锁和不加锁。

2.然后我们再来理解一下wait的操作

1.先释放锁
2.进行阻塞等待
3.收到通知后,重新尝试获取锁,并且在获取锁后,继续向下执行

很显然在上面的代码中,我们可以看出上面处于不加锁状态,不满足第一个条件,现在连锁都没有,哪里还需要释放锁,这就好比,你还单身就想着要分手,还是先找个对象再说吧,因此wait操作需要搭配synchronized来一起使用

  1. public class test2 {
  2. public static void main(String[] args) throws InterruptedException {
  3. Object object = new Object();
  4. synchronized (object) {
  5. System.out.println("wait 之前");
  6. object.wait();
  7. System.out.println("wait 之后");
  8. }
  9. }

代码结果:

在这里插入图片描述

1.object.wait():虽然这里的wait是阻塞,阻塞在synchronized的代码块当中,但是实际上已经释放锁,其他线程是可以获取这个object对象的

2.上面的代码中,我们会发现一个问题,因为没有notify,所以上面的在打印“wait之前”后,该线程会一直死等下去,这样显然不是一个很好的结果,所以wait会有带参数版本和没有参数版本,wait有参数版本,指定了等待的最大时间,这和sleep有点相似,但是是有本质差别

虽然两者都能等待时间,虽然都可以被提前唤醒(wait可以使用notify唤醒,sleep可以使用interrupt唤醒),但是这两种唤醒的含义是不一样的,notify唤醒wait,是不会有异常的,但是interrupt唤醒sleep则表示出现了异常

2.notify

1.notify唤醒线程

notify 方法是唤醒等待的线程

2.notify的使用

示例代码:

  1. public class test3 {
  2. public static void main(String[] args) throws InterruptedException {
  3. Object object = new Object();
  4. Thread t1 = new Thread(() -> {
  5. // 这个线程负责进行等待
  6. System.out.println("t1: wait 之前");
  7. try {
  8. synchronized (object) {
  9. object.wait();
  10. }
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println("t1: wait 之后");
  15. });
  16. Thread t2 = new Thread(() -> {
  17. System.out.println("t2: notify 之前");
  18. synchronized (object) {
  19. // notify 务必要获取到锁, 才能进行通知
  20. try {
  21. Thread.sleep(3000);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. object.notify();
  26. }
  27. System.out.println("t2: notify 之后");
  28. });
  29. t1.start();
  30. Thread.sleep(500);
  31. t2.start();
  32. }
  33. }

运行结果

在这里插入图片描述

1.在上面的示例的代码中,下面后框的四个对象都相同,才能够正确生效,这里的notify()和wait()进行配对,使用相同对象,如果不同就不会生效

在这里插入图片描述

2.Thread.sleep(500) 是因为如果不添加这个,只有t1.start()和t2.start(),由于进程调度的随机性,不一定保证先执行wait,然后执行notify,如果此时调用notify,没有wait,那么此处的wait就无法被唤醒,但是如果你的电脑比较卡,有可能sleep(500)可能还不行,需要更长时间。

3.notifyAll()的使用

上面notify方法只是唤醒某一个等待线程,这个显然是不够用的,使用notifyAll方法可以一次唤醒所有的等待线程

注意: 虽然是同时唤醒 3 个线程, 但是这 3 个线程需要竞争锁。 所以并不是同时执行, 而仍然是有先有后的执行

  1. public class test3 {
  2. public static void main(String[] args) throws InterruptedException {
  3. Object object = new Object();
  4. Thread t1 = new Thread(() -> {
  5. // 这个线程负责进行等待
  6. System.out.println("t1: wait 之前");
  7. try {
  8. synchronized (object) {
  9. object.wait();
  10. }
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println("t1: wait 之后");
  15. });
  16. Thread t2 = new Thread(() -> {
  17. // 这个线程负责进行等待
  18. System.out.println("t2: wait 之前");
  19. try {
  20. synchronized (object) {
  21. object.wait();
  22. }
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. System.out.println("t2: wait 之后");
  27. });
  28. Thread t3 = new Thread(() -> {
  29. // 这个线程负责进行等待
  30. System.out.println("t3: wait 之前");
  31. try {
  32. synchronized (object) {
  33. object.wait();
  34. }
  35. } catch (InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. System.out.println("t3: wait 之后");
  39. });
  40. Thread t4 = new Thread(() -> {
  41. System.out.println("t4: notifyAll 之前");
  42. synchronized (object) {
  43. // notify 务必要获取到锁, 才能进行通知
  44. try {
  45. Thread.sleep(3000);
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. object.notifyAll();
  50. }
  51. System.out.println("t4: notifyAll 之后");
  52. });
  53. t1.start();
  54. t2.start();
  55. t3.start();
  56. Thread.sleep(500);
  57. t4.start();
  58. }
  59. }

代码结果

在这里插入图片描述


总结

在Java中,wait和notify是两个用于线程间通信的方法,它们通常与synchronized关键字一起使用,需要注意的是,wait、notify和notifyAll方法必须在同步代码块或同步方法中调用,否则会抛出IllegalMonitorStateException异常。

发表评论

表情:
评论列表 (有 0 条评论,104人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Wait-Notify机制

    http://www.ticmy.com/?p=219 Wait-Notify机制可以说是实现阻塞操作较为高效的一种方式。虽然在实际中鼓励使用类库中已有的满足条件的类,或基于