Java多线程之线程协作

一、前言

  上一节提到,如果有一个线程正在运行synchronized 方法,那么其他线程就无法再运行这个方法了。这就是简单的互斥处理。

  假如我们现在想执行更加精确的控制,而不是单纯地等待其他线程运行终止,例如下面这样的控制。

  ● 如果空间为空则写入数据;如果非空则一直等待到变空为止

  ● 空间已为空时,“通知”正在等待的线程

  此处是根据“空间是否为空”这个条件来执行线程控制的。Java 提供了用于执行线程控制的wait 方法、notify 方法和notifyAll 方法。wait 是让线程等待的方法,而notify 和notifyAll 是唤醒等待中的线程的方法。

二、等待队列——线程休息室

  在学习wait、notify 和notifyAll 之前,我们先来学习一下等待队列。所有实例都拥有一个等待队列,它是在实例的wait方法执行后停止操作的线程的队列。打个比方来说,就是为每个实例准备的线程休息室。

  在执行wait 方法后,线程便会暂停操作,进入等待队列这个休息室。除非发生下列某一情况,否则线程会一直在等待队列中休眠。当下列任意一种情况发生时,线程便会退出等待队列。

  ● 有其他线程的notify方法来唤醒线程

  ● 有其他线程的notifyAll方法来唤醒线程

  ● 有其他线程的interrupt方法来唤醒线程

  ● wait方法超时

  下面以图配文依次谈谈wait、notify 和notifyAll。而关于interrupt 方法和wait 方法的超时,将会在后面的篇幅中谈谈。

三、wait 方法——将线程放入等待队列

  wait(等待)方法会让线程进入等待队列。假设我们执行了下面这条语句。

  obj.wait();

  那么,当前线程便会暂停运行,并进入实例obj的等待队列中。这叫作“线程正在obj 上wait”。如果实例方法中有如下语句(1),由于其含义等同于(2),所以执行了wait() 的线程将会进入this 的等待队列中,这时可以说“线程正在this 上wait”。

  wait();  (1)

  this.wait(); (2)

  若要执行wait方法,线程必须持有锁(这是规则)。但如果线程进入等待队列,便会释放其实例的锁。整个操作过程如下图所示。

  • 关于等待队列

  等待队列是一个虚拟的概念。它既不是实例中的字段,也不是用于获取正在实例上等待的线程的列表的方法。

  • 获取锁了的线程A执行wait方法:

  • 线程A进入等待队列,释放锁:

  • 线程B能够获取锁:

四、notify 方法——从等待队列中取出线程

  notify(通知)方法会将等待队列中的一个线程取出。假设我们执行了下面这条语句。

  obj.notify();

  那么obj 的等待队列中的一个线程便会被选中和唤醒,然后就会退出等待队列。

  整个操作过程如下所示。

  • 获取锁了的线程B执行notify方法:

  • 线程A退出等待队列,想要进入wait的下一个操作,但刚才执行notify方法的线程B任持有着锁

  • 刚才执行notify的线程B释放了锁

  • 退出等待队列的线程A获取锁,执行wait的下一步操作

  同wait 方法一样,若要执行notify 方法,线程也必须持有要调用的实例的锁(这是规则)。

  执行notify 后的线程状态:

  notify 唤醒的线程并不会在执行notify 的一瞬间重新运行。因为在执行notify 的那一瞬间,执行notify 的线程还持有着锁,所以其他线程还无法获取这个实例的锁(如第二幅图所示)。

  执行notify 后如何选择线程?

  假如在执行notify 方法时,正在等待队列中等待的线程不止一个,对于“这时该如何来选择线程”这个问题规范中并没有作出规定。究竟是选择最先wait 的线程,还是随机选择,或者采用其他方法要取决于Java 平台运行环境。因此编写程序时需要注意,最好不要编写依赖于所选线程的程序。

五、notifyAll 方法——从等待队列中取出所有线程

  notifyAll(通知大家)方法会将等待队列中的所有线程都取出来。例如,执行下面这条语句之后,在obj 实例的等待队列中休眠的所有线程都会被唤醒。

  obj.notifyAll();

  如果简单地在实例方法中写成下面(1)这样,那么由于其含义等同于(2),所以该语句所在方法的实例(this)的等待队列中所有线程都会退出等待队列。

  notifyAll();  (1)

  this.notifyAll(); (2)

  下面两幅图展示了notify 方法和notifyAll 方法的差异。notify 方法仅唤醒一个线程,而notifyAll 则唤醒所有线程,这是两者之间唯一的区别。

  • notify方法仅唤醒一个线程,并让该线程退出等待对列:

  • notifyAll方法唤醒所有线程,并让所有线程都退出等待队列

  同wait 方法和notify 方法一样,notifyAll 方法也只能由持有要调用的实例的锁的线程调用。

  刚被唤醒的线程会去获取其他线程在进入wait 状态时释放的锁。但现在锁是在谁的手中呢?对,就是执行notifyAll 的线程正持有着锁。因此,唤醒的线程虽然都退出了等待队列,但都在等待获取锁,处于阻塞状态。只有在执行notifyAll 的线程释放锁以后,其中一个幸运儿才能够实际运行。

  那如果线程未持有锁会怎样呢?

  如果未持有锁的线程调用wait、notify 或notifyAll, 异常java.lang.IllegalMonitorStateException会被抛出。

  该使用notify 方法还是notifyAll 方法呢?

  notify 方法和notifyAll 方法非常相似,到底该使用哪一个呢?实际上,这很难选择。

  由于notify 唤醒的线程较少,所以处理速度要比使用notifyAll 时快。

  但使用notify 时,如果处理不好,程序便可能会停止。一般来说,使用notifyAll 时的代码要比使用notify 时的更为健壮。

  除非开发人员完全理解代码的含义和范围,否则使用notifyAll 更为稳妥。使用notify时发生问题的示例将在后面探讨,详情可以关注我的博文。

六、wait、notify、notifyAll 是Object 类的方法

  wait、notify 和notifyAll 都是java.lang.Object 类的方法,而不是Thread 类中固有的方法。

  下面再来回顾一下wait、notify 和notifyAll 的操作。

  ● obj.wait()是将当前线程放入obj的等待队列中

  ● obj.notify()会从obj的等待队列中唤醒一个线程

  ● obj.notifyAll()会从obj的等待队列中唤醒所有线程

  换句话说, wait、notify 和notifyAll 这三个方法与其说是针对线程的操作,倒不如说是针对实例的等待队列的操作。由于所有实例都有等待队列,所以wait、notify 和notifyAll也就成为了Object 类的方法。

  wait、notify、notifyAll 也是Thread 类的方法:

  wait、notify 和notifyAll 确实不是Thread 类中固有的方法。但由于Object 类是Java 中所有类的父类,所以也可以说wait、notify 和notifyAll 都是Thread 类的方法。关于wait、notify 和notifyAll 的用法,后面的篇幅中将会详细解说。

  参考:图解Java多线程设计模式

Java多线程之线程协作的更多相关文章

  1. Java多线程之线程的生命周期

    Java多线程之线程的生命周期 一.前言 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(R ...

  2. Java多线程之线程其他类

    Java多线程之线程其他类 实际编码中除了前面讲到的常用的类之外,还有几个其他类也有可能用得到,这里来统一整理一下: 1,Callable接口和Future接口 JDK1.5以后提供了上面这2个接口, ...

  3. Java多线程之线程的通信

    Java多线程之线程的通信 在总结多线程通信前先介绍一个概念:锁池.线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池.每个对象都有自己的锁池的空间,用于放置等待运行的线程.这些 ...

  4. Java多线程之线程的同步

    Java多线程之线程的同步 实际开发中我们也经常提到说线程安全问题,那么什么是线程安全问题呢? 线程不安全就是说在多线程编程中出现了错误情况,由于系统的线程调度具有一定的随机性,当使用多个线程来访问同 ...

  5. Java多线程之线程的控制

    Java多线程之线程的控制 线程中的7 种非常重要的状态:  初始New.可运行Runnable.运行Running.阻塞Blocked.锁池lock_pool.等待队列wait_pool.结束Dea ...

  6. Java多线程父子线程关系 多线程中篇(六)

    有的时候对于Java多线程,我们会听到“父线程.子线程”的概念. 严格的说,Java中不存在实质上的父子关系 没有方法可以获取一个线程的父线程,也没有方法可以获取一个线程所有的子线程 子线程的消亡与父 ...

  7. 关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

    Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...

  8. Java多线程02(线程安全、线程同步、等待唤醒机制)

    Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...

  9. JAVA多线程之线程间的通信方式

    (转发) 收藏 记 周日,北京的天阳光明媚,9月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...

随机推荐

  1. 你真的熟练使用webpack吗?

    https://www.webpackjs.com/ 官网地址 当自己在简历中写着熟练使用webpack的时候,殊不知自己只是在vue脚手架,react脚手架的路上走着比较轻松而已. 当面试官问你这几 ...

  2. 【iOS】更换证书遇到的问题

    今天给一个项目换证书的时候遇到了这个问题: Code Sign error: Provisioning profile does not match bundle identifier: The pr ...

  3. GitHub 用户排行榜

    排行榜预览网址:Github | Githack | UNPKG | Gitee Github 中国用户排名,全球仓库 Star 最多排名,通过 Github API v3 来生成页面数据,排行榜预览 ...

  4. 关于定时器Scheduled(cron)的问题

    定时器配置步骤参考:http://blog.csdn.NET/sd4000784/article/details/7745947 下面给出cron参数中各个参数的含义: CRON表达式    含义 & ...

  5. 自定义SWT控件一之自定义单选下拉框

    一.自定义下拉控件 自定义的下拉框,是自定义样式的,其中的下拉框使用的是独立的window,非复选框的下拉框双击单机其它区域或选择完之后,独立window构成的下拉框会自动消失. package co ...

  6. 干货来了!python学习之重难点整理合辑1

    关于装饰器.lambda.鸭子类型.魔法函数的理解仍存有困惑之处,趁周末有时间温故,赶紧去自学了解下相关知识. 1.装饰器是什么: 很多初学者在接触装饰器的时候只做到了肤浅的了解它的概念.组成形态.实 ...

  7. adb 下载安装

    1.官网下载:选择自己电脑对应的版本 https://www.androiddevtools.cn/#   SDK Tools, SDK platfrom  Tools(解压在sdk 的根目录下) 2 ...

  8. ipv6的连接

    基础知识不说了,网上一大堆! 基本内容不说了,写字太累了! 只说三点细节,记住就行: 1.ff开头的是多播地址,只能用于udp多播 2.fe80开头的是本地link地址,不管ping也好,connec ...

  9. 一文搞懂 Prometheus 的直方图

    原文链接:一文搞懂 Prometheus 的直方图 Prometheus 中提供了四种指标类型(参考:Prometheus 的指标类型),其中直方图(Histogram)和摘要(Summary)是最复 ...

  10. 【Java例题】2.6 三角形的面积

    6. 用海伦公式计算三角形的面积. 设边长分别时a,b和c,s=(a+b+c)/2, 则三角形面积area=sqrt(s*(s-a)*(s-b)*(s-c)). package study; impo ...