Java多线程之线程协作
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月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...
随机推荐
- ArcGIS API For JavaScript 开发(一)环境搭建
标签:B/S结构开发,Asp.Net开发,WebGIS开发 前言:为什么写这个,一是学习:二是分享,共同进步,毕竟也是在这个园子里学到了很多: (一)环境搭建 集成开发环境:VS2013 Ultima ...
- python中的元类(metaclass)
认识python中元类的准备工作. 1,首先需要明白一个概念就是python中一切皆为对象. input: class Trick(object): pass ') print type(1234) ...
- java练习---1
//程序员:罗元昊 2017.9.6public class Ap{ public static void main(String[] args){ System.out.println(" ...
- centos7下yum方式安装MySQL5.7
前言: MySQL作为一款免费.开源数据库产品,已经问世就饱受关注,很多中小企业甚至是大企业都钟爱MySQL,随着大数据的不断发展,我们接触的信息量也越来越多,虽然NoSQL是大数据的宠儿,但MySQ ...
- 金蝶K3 V12.2版本,中途启用双计量单位出现错误
忘记修改虚仓库存/收料通知单的双计量数量
- T-SQL 恢复数据库
USE master GO ALTER DATABASE DEMO SET SINGLE_USER GO ALTER DATABASE DEMO SET EMERGENCY GO DBCC CHECK ...
- vue中el-upload上传多图片且携带参数,批量而不是一张一张的解决方案
现在前端基本不是vue技术栈就是react技术栈. vue技术栈最常用的就是element-ui的ui框架了. 在项目中,我们经常会碰到这种需求:批量上传文件 element-ui 确实也为我们提供了 ...
- http://regex.alf.nu/ 非标准答案
Plain strings (207) foo Anchors (206) ...
- basic
vmware三种网络类型 bridged(桥接)通过物理主机网卡架设桥,从而连入实际网络,最接近正式网络环境 NAT(地址转换)虚拟机通过宿主机转发地址上网,宿主机ip更改虚拟机不需要改.过程:虚拟机 ...
- 实时同步lsyncd
实时同步lsyncd 1 lsyncd 1.1 lsyncd 简介 Lsyncd使用文件系统事件接口(inotify或fsevents)来监视对本地文件和目录的更改.Lsyncd将这些事件整理几秒钟, ...