线程间的通信 wait、notify

ゝ一纸荒年。 2022-10-06 14:56 229阅读 0赞

上一篇:
线程同步之synchronized关键字

wait

wait跟sleep方法的作用一样,也是让线程休眠,但是针对的对象不同。

  • sleep: sleep是Thread类中的静态方法,是直接作用于线程的,在哪个线程调sleep,哪个线程就休眠,而且可以随时调用。sleep方法执行期间不会释放锁,其他线程也必须要等待sleep执行完毕后才有机会进入同步代码中。
  • wait: wait是Object类中的一个普通方法,通过对象调用。但是必须在持有该对象锁的同步代码中调用,否则会抛出异常。wait方法会释放锁,其他线程可以在wait期间进入同步代码中。

我们先来看看Object对象中的方法,本篇博客需要关心的就是以下红框中的几个方法。

在这里插入图片描述

再来看看wait源码
在这里插入图片描述

可以看到,最终调用了重载的 wait(long timeout)

在这里插入图片描述

我们先来简单用一用,看一下跟sleep的区别:

  1. public static void main(String[] args) {
  2. Object o = new Object();
  3. for (int i = 0; i < 5; i++) {
  4. new Thread(() -> {
  5. synchronized (o) {
  6. System.out.println(Thread.currentThread().getName() + "开始执行");
  7. try {
  8. TimeUnit.SECONDS.sleep(2);
  9. // o.wait(2000);
  10. System.out.println(Thread.currentThread().getName() + "被唤醒");
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println(Thread.currentThread().getName() + "执行完毕");
  15. }
  16. }).start();
  17. }
  18. }

这里我们先在5个线程中的同步代码块中使用sleep看一下:
在这里插入图片描述

同样的代码,把sleep改成wait试一下,这要注意wait方法必须要在持有对象锁的同步代码块中调用,也就是说你想调用o.wait,那么你必须先锁住o。

  1. public static void main(String[] args) {
  2. Object o = new Object();
  3. for (int i = 0; i < 5; i++) {
  4. new Thread(() -> {
  5. synchronized (o) {
  6. System.out.println(Thread.currentThread().getName() + "开始执行");
  7. try {
  8. // TimeUnit.SECONDS.sleep(2);
  9. o.wait(2000);
  10. System.out.println(Thread.currentThread().getName() + "被唤醒");
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println(Thread.currentThread().getName() + "执行完毕");
  15. }
  16. }).start();
  17. }
  18. }

运行结果如下:
在这里插入图片描述

对比两个运行结果,可以很明显的看出来,sleep是不会释放锁的,休眠期间其他线程必须等待锁被释放后才能执行,wait是会释放锁的,其他线程会在wait期间走进同步代码中。

上面我们是给wait指定了等待时间,当我们不给wait指定时间时,则线程会一直处于等待状态,直到调用了同一个对象锁的notifynotifyAll方法,才会继续执行。

示例代码:
不给wait指定等待时间

  1. public static void main(String[] args) {
  2. Object o = new Object();
  3. for (int i = 0; i < 5; i++) {
  4. new Thread(() -> {
  5. synchronized (o) {
  6. System.out.println(Thread.currentThread().getName() + "开始执行");
  7. try {
  8. o.wait();
  9. System.out.println(Thread.currentThread().getName() + "被唤醒");
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println(Thread.currentThread().getName() + "执行完毕");
  14. }
  15. }).start();
  16. }
  17. }

运行结果:
在这里插入图片描述
可以看到,后面一直都没执行。

notify

源码注释太长,这里就不贴了。
我们只需要记住,notify是用来唤醒正在等待的单个线程,如果同一把锁有多个线程都在等待,则随机唤醒其中一个。
我们来简单用一用:

  1. public static void main(String[] args) {
  2. Object o = new Object();
  3. for (int i = 0; i < 5; i++) {
  4. new Thread(() -> {
  5. synchronized (o) {
  6. System.out.println(Thread.currentThread().getName() + "开始执行");
  7. try {
  8. o.wait();
  9. System.out.println(Thread.currentThread().getName() + "被唤醒");
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println(Thread.currentThread().getName() + "执行完毕");
  14. }
  15. }).start();
  16. }
  17. /*让主线程休眠2秒钟*/
  18. try {
  19. TimeUnit.SECONDS.sleep(2);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. synchronized (o) {
  24. o.notify();
  25. }
  26. }

看一下运行结果:

在这里插入图片描述

可以看到,只有一条线程被唤醒了。

notifyAll

notify是用来唤醒正在等待的所有线程,如果同一把锁有多个线程都在等待,则唤醒所有线程。

来用一把:

  1. public static void main(String[] args) {
  2. Object o = new Object();
  3. for (int i = 0; i < 5; i++) {
  4. new Thread(() -> {
  5. synchronized (o) {
  6. System.out.println(Thread.currentThread().getName() + "开始执行");
  7. try {
  8. o.wait();
  9. System.out.println(Thread.currentThread().getName() + "被唤醒");
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println(Thread.currentThread().getName() + "执行完毕");
  14. }
  15. }).start();
  16. }
  17. /*让主线程休眠2秒钟*/
  18. try {
  19. TimeUnit.SECONDS.sleep(2);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. synchronized (o) {
  24. o.notifyAll();
  25. }
  26. }

运行结果如下:
在这里插入图片描述

可以看到,所有wait的线程都被唤醒了。

实际上,Thread中的join方法底层调用的就是wait方法,join源码如下,join本身就是个同步方法。

在这里插入图片描述
那join方法是什么时候会被唤醒呢?

在这里插入图片描述
通过注释可以知道,是在线程处于结束状态后就会被唤醒。

之前我们在 从Java线程到kotlin协程之线程合并 (join) 这篇文章中我们用join来实现两个接口

那这样一来,我们可以使用wait来达到跟join一样的效果,实现线程的同步。
代码如下:

先来创建两个Runnable来模拟请求接口

  1. /**
  2. * 模拟请求高德接口
  3. */
  4. class GaoDeRunnable implements Runnable {
  5. /*模拟的锁对象*/
  6. Object lock = null;
  7. /*接口返回的结果*/
  8. private String result = "";
  9. public GaoDeRunnable(Object lock) {
  10. this.lock = lock;
  11. }
  12. public String getResult() {
  13. return result;
  14. }
  15. public void setResult(String result) {
  16. this.result = result;
  17. }
  18. @Override
  19. public void run() {
  20. /*模拟请求高德接口*/
  21. synchronized (lock) {
  22. System.out.println("高德请求开始执行....");
  23. try {
  24. TimeUnit.SECONDS.sleep(2);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. result = "gaodeResult";
  29. System.out.println("高德接口请求完毕,唤醒等待的线程");
  30. /*执行完毕 唤醒等待的线程*/
  31. lock.notifyAll();
  32. }
  33. }
  34. }
  35. /**
  36. * 模拟请求百度接口
  37. */
  38. class BaiDuRunnable implements Runnable {
  39. Object lock = null;
  40. private String result = "";
  41. public BaiDuRunnable(Object lock) {
  42. this.lock = lock;
  43. }
  44. public String getResult() {
  45. return result;
  46. }
  47. public void setResult(String result) {
  48. this.result = result;
  49. }
  50. @Override
  51. public void run() {
  52. /*模拟请求高德接口*/
  53. synchronized (lock) {
  54. System.out.println("百度请求开始执行....");
  55. try {
  56. TimeUnit.SECONDS.sleep(1);
  57. } catch (InterruptedException e) {
  58. e.printStackTrace();
  59. }
  60. result = "baiduResult";
  61. System.out.println("百度接口请求完毕,唤醒等待的线程");
  62. lock.notifyAll();
  63. }
  64. }
  65. }

两个代码逻辑都一样,休眠一段时间后,唤醒wait的线程。

main方法代码如下:

  1. public static void main(String[] args) {
  2. /*高德 锁*/
  3. Object gaodeObjLock = new Object();
  4. /*百度 锁*/
  5. Object baiduObjLock = new Object();
  6. /*模拟请求高德接口的Runnable*/
  7. GaoDeRunnable gaoDeRunnable = new GaoDeRunnable(gaodeObjLock);
  8. /*模拟请求百度接口的Runnable*/
  9. BaiDuRunnable baiDuRunnable = new BaiDuRunnable(baiduObjLock);
  10. /*启动两个线程去执行*/
  11. new Thread(gaoDeRunnable).start();
  12. new Thread(baiDuRunnable).start();
  13. /*等待高德Runnable返回数据*/
  14. synchronized (gaodeObjLock) {
  15. while (gaoDeRunnable.getResult().isEmpty()) {
  16. try {
  17. gaodeObjLock.wait();
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. /*高德接口执行完毕*/
  23. System.out.println("高德接口执行完毕,获取的结果是:" + gaoDeRunnable.getResult());
  24. }
  25. /*等待百度Runnable返回数据*/
  26. synchronized (baiduObjLock) {
  27. while (baiDuRunnable.getResult().isEmpty()) {
  28. try {
  29. baiduObjLock.wait();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. /*高德接口执行完毕*/
  35. System.out.println("百度接口执行完毕,获取的结果是:" + baiDuRunnable.getResult());
  36. }
  37. System.out.println("两个接口都执行完毕了,最终的数据是:" + gaoDeRunnable.getResult() + "-----" + baiDuRunnable.getResult());
  38. }

下面来看看运行效果:

在这里插入图片描述

可以看到,运行结果跟之前使用join是一样的。

好了,wait方法就是这些

下一篇:

JUC以及并发,线程同步,线程安全的概念加深


如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!

发表评论

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

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

相关阅读

    相关 线通信

    1、什么是多线程之间通信 多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 于是我们引出了等待唤醒机制:

    相关 线通信Objectwaitnotify

    wait/notify等待通知方式 等待通知机制就是将处于等待状态的线程将由其它线程发出通知后重新获取CPU资源,继续执行之前没有执行完的任务。最典型的例子生产者–消费者

    相关 线通信

    为什么需要线程通讯 线程是操作系统调度的最小单位,有自己的栈空间,可以按照既定的代码逐步的执行,但是如果每个线程间都孤立的运行,那就会造资源浪费。所以在现实中,我们需要这

    相关 线通信

      https://www.cnblogs.com/hapjin/p/5492619.html 通常可用把并行程序理解为一组相互独立的、能够发关和接收消息的组件,这也称为角

    相关 线通信

    线程和线程之间不是独立的个体,它们彼此之间可以互相通信和协作。 线程通信就是在线程之间传递信息,保证他们能够协同工作。在线程间进行通信后,系统之间的交互性会更强大,在大大提高

    相关 线通信

    注意: 必须在同步方法中使用wait和notify方法,因为执行wait和notify的前提条件是必须持有同步方法(或块)的monitor的所有权,否则将会抛出异常

    相关 线通信

    典型例子就是生产者-消费者模式,仓库为空时,消费者无法从仓库调动产品,只能wait,直到仓库有产品时被notify;仓库满了时,生产者则暂时停止生产(wait),直到仓库有空被