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. vue教程(一)-html使用vue

    前后端分离.微服务框架是当下比较流行的词汇,而vue就是前端框架的佼佼者.下面重点介绍一下vue的用法: vue起步:1.引包    2.启动new Vue({el:目的地,template:模板内容 ...

  2. Java--随机数和随机数种子(转)

    在计算机中并没有一个真正的随机数发生器,但是可以做到使产生的数字重复率很低,这样看起来好象是真正的随机数,实现这一功能的程序叫伪随机数发生器. 有关如何产生随机数的理论有许多,如果要详细地讨论,需要厚 ...

  3. springMVC保存数据到mysql数据库中文乱码问题解决方法

    1.web.xml中添加过滤器 <filter> <filter-name>CharacterEncodingFilter</filter-name> <fi ...

  4. PyCharm 配置 Git 教程

    之前给大家介绍了 Git 安装及使用指南,今天再给大家介绍一下在 PyCharm 中使用 Git. 1 打开 File -> Settings -> Version Control -&g ...

  5. spring-boot-plus集成Spring Boot Admin管理和监控应用

    Spring Boot Admin Spring Boot Admin用来管理和监控Spring Boot应用程序 应用程序向我们的Spring Boot Admin Client注册(通过HTTP) ...

  6. Hadoop MapReduce的Shuffle过程

    一.概述 理解Hadoop的Shuffle过程是一个大数据工程师必须的,笔者自己将学习笔记记录下来,以便以后方便复习查看. 二. MapReduce确保每个reducer的输入都是按键排序的.系统执行 ...

  7. HC-08 BLE资料

    1.1 特点简介 HC-08蓝牙串口通信模块是新一代的基于Bluetooth Specification V4.0 BLE蓝牙协议的数传模块.无线工作频段为 2.4GHz ISM,调制方式是 GFSK ...

  8. .net持续集成测试篇之Nunit文件断言、字符串断言及集合断言

    使用前面讲过的方法基本上能够完成工作中的大部分任务了,然而有些功能实现起来还是比较麻烦的,比如说字符串相等性比较不区分大小写,字符串是否匹配某一正则规则,集合中的每一个(某一个)元素是否符合特定规则等 ...

  9. HDP Hive性能调优

    (官方文档翻译整理及总结) 一.优化数据仓库 ① Hive LLAP  是一项接近实时结果查询的技术,可用于BI工具以及网络看板的应用,能够将数据仓库的查询时间缩短到15秒之内,这样的查询称之为Int ...

  10. webupload项目中使用

    目前项目需要一个多图上传的功能,使用LayUI并也是可以实现多图上传的,但是没有图片删除功能,参考了一下网上多图上传的插件,选择了WebUpload进行功能开发. 然而不幸的是,官方的插件并不带UI界 ...