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多线程之线程协作的更多相关文章
-
Java多线程之线程的生命周期
Java多线程之线程的生命周期 一.前言 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(R ...
-
Java多线程之线程其他类
Java多线程之线程其他类 实际编码中除了前面讲到的常用的类之外,还有几个其他类也有可能用得到,这里来统一整理一下: 1,Callable接口和Future接口 JDK1.5以后提供了上面这2个接口, ...
-
Java多线程之线程的通信
Java多线程之线程的通信 在总结多线程通信前先介绍一个概念:锁池.线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池.每个对象都有自己的锁池的空间,用于放置等待运行的线程.这些 ...
-
Java多线程之线程的同步
Java多线程之线程的同步 实际开发中我们也经常提到说线程安全问题,那么什么是线程安全问题呢? 线程不安全就是说在多线程编程中出现了错误情况,由于系统的线程调度具有一定的随机性,当使用多个线程来访问同 ...
-
Java多线程之线程的控制
Java多线程之线程的控制 线程中的7 种非常重要的状态: 初始New.可运行Runnable.运行Running.阻塞Blocked.锁池lock_pool.等待队列wait_pool.结束Dea ...
-
Java多线程父子线程关系 多线程中篇(六)
有的时候对于Java多线程,我们会听到“父线程.子线程”的概念. 严格的说,Java中不存在实质上的父子关系 没有方法可以获取一个线程的父线程,也没有方法可以获取一个线程所有的子线程 子线程的消亡与父 ...
-
关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)
Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...
-
Java多线程02(线程安全、线程同步、等待唤醒机制)
Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...
-
JAVA多线程之线程间的通信方式
(转发) 收藏 记 周日,北京的天阳光明媚,9月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...
随机推荐
-
[干货来袭]MSSQL Server on Linux预览版安装教程(先帮大家踩坑)
前言 昨天晚上微软爸爸开了全国开发者大会,会上的内容,我就不多说了,园子里面很多.. 我们唐总裁在今年曾今透漏过SQL Server love Linux,果不其然,这次开发者大会上就推出了MSSQL ...
-
Java 对象销毁
Java语言拥有一套完整的垃圾回收机制. 何种对象会被java虚拟机视为垃圾.主要包括以下两种情况: (1)对象引用超过其作用范围,则这个对象将被视为垃圾 (2)将对象赋值为null 参考资料:Jav ...
-
JAVA字节码解析
Java字节码指令 Java 字节码指令及javap 使用说明 ### java字节码指令列表 字节码 助记符 指令含义 0x00 nop 什么都不做 0x01 aconst_null 将null推送 ...
-
Java 基础之认识 Annotation
Java 基础之认识 Annotation 从 JDK 1.5 版本开始,Java 语言提供了通用的 Annotation 功能,允许开发者定义和使用自己的 Annotation 类型.Annotat ...
-
SpringMVC(一)
开始学习SpringMVC了,就写下每次学习的内容,以及自己的理解.方便以后回顾知道自己哪里好哪里不好~~~ 一.目录 1.主目录如此: 2.target目录 二.文件 1.主要用到的几个文件夹(如主 ...
-
Properties的读取和写入
Properties是HashTable下的一个持久的属性集,没有泛型,key-value都是String类型.由于能与IO流结合使用,所以能方便地操作属性文件或者xml文件. 一.propertie ...
-
Demo 示例控制输入光标位置
<!DOCTYPE html> <html> <head> <meta charset=utf-8 /> <meta name="aut ...
-
IE浏览器打印的页眉页脚设置解决方法
首先说明问题: 默认情况下,通过IE的打印对话框,打印出来的内容都有页眉和页脚的. 查看ie的页面设置发现如右图中,页眉页脚 下面先说明&w&bPage&p of &P ...
-
运用jieba库 寻找高频词
一.准备 1.首先 先用cmd 安装 jieba库,输入 pip install jieba 2.其次 本次要用到wordcloud库和 matplotlib库,也在cmd输入pip install ...
-
org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (Access denied for user &#39;root&#39;@&#39;localhost&#39; (using password:
tationProcessor' to allow for resolving potential circular referencesDEBUG 2018-05-28 11:32:35,016 o ...