使用wait/notify实现线程间的通信
之前对Java多线程中的wait/notify机制理解都不是很清晰,最近看了一本技术书,通过里面的讲解再配上一些博客,终于算是对wait/notify有了进一步的理解。
下面就来说说我对这两个方法的认识:
都知道在Java中,我们可以通过继承Thread或者实现Runnable接口来实现多线程,这些线程会各自执行自己的任务,但是一个人的力量是有限的,一个线程的力量也是有限的,要想使系统各部分配合得更好,我们需要实现各个线程间的通信。要实现线程间的通信最好的方法就是使用wait/notify机制。
这里有两个新问题:
- 什么是线程间的通信?
- 如何使用wait/notify机制实现线程间的通信?
为了解决这两个问题,我们来看看下面这段代码:
public class ThreadA extends Thread{
private MyList list;
public ThreadA(MyList list) {
this.list = list;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
list.add();
System.out.println("第 "+i+" 此执行add操作");
}
}
public static void main(String[] args) {
MyList list = new MyList();
ThreadA a = new ThreadA(list);
ThreadB b = new ThreadB(list);
a.start();
b.start();
}
}
class ThreadB extends Thread{
private MyList list;
public ThreadB(MyList list) {
this.list = list;
}
@Override
public void run() {
while( true ) {
if (list.size() == 3) {
System.out.println("线程Thread检测到list中的长度变为3了,停止该线程");
Thread.interrupted();
}
}
}
}
class MyList{
private ArrayList<String> list = new ArrayList<String>();
public void add() {
list.add("abc");
}
public int size() {
return list.size();
}
}
执行结果如下:
第 0 此执行add操作
第 1 此执行add操作
第 2 此执行add操作
线程Thread检测到list中的长度变为3了,停止该线程
第 3 此执行add操作
第 4 此执行add操作
在上面的例子中实现了两个线程,其中线程ThreadA的任务是向list中添加值,而线程ThreadB的任务是监听list中的数量,一旦list的长度达到了3,就通过interrupted方法停止该线程的监听操作。
在这里线程ThreadA与线程ThreadB就实现了通信,这个例子中的通信指的就是:线程ThreadA执行向list添加值的操作,而线程ThreadB通过不断的执行while监听来观察线程ThreadA的操作,从而做出自己相应的操作。可是这种通信方式有一个弊端,线程ThreadB需要不断的执行while循环来达到监听的目的,这样对CPU的资源会产生浪费。服务端的资源是非常宝贵的,所以就出现了wait/notify机制来解决这个问题。
我们看看使用wait/notify机制后,上面的线程间通信会变成什么样?
public class ThreadA extends Thread{
private MyList list;
public ThreadA(MyList list) {
this.list = list;
}
@Override
public void run() {
synchronized (list) {
for (int i = 0; i < 5; i++) {
list.add();
System.out.println("第 "+i+" 此执行add操作");
try {
if (list.size() == 3)
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
MyList list = new MyList();
ThreadA a = new ThreadA(list);
ThreadB b = new ThreadB(list);
a.start();
b.start();
}
}
class ThreadB extends Thread{
private MyList list;
public ThreadB(MyList list) {
this.list = list;
}
@Override
public void run() {
synchronized (list) {
System.out.println("线程Thread检测到list中的长度变为3了,停止该线程");
list.notify();
System.out.println("notify方法调用结束");
}
}
}
class MyList{
private ArrayList<String> list = new ArrayList<String>();
public void add() {
list.add("abc");
}
public int size() {
return list.size();
}
}
执行结果如下:
第 0 此执行add操作
第 1 此执行add操作
第 2 此执行add操作
线程Thread检测到list中的长度变为3了,停止该线程
notify方法调用结束
第 3 此执行add操作
第 4 此执行add操作
上面是使用了wait/notify机制后的代码实现,两种方式的执行结果相同。但是代码的实现方式却大有不同,可以看到线程ThreadB里没有了while循环,说明在该线程里不用一直监听list中的值了,所以资源浪费的问题得到了解决,这其中的原理是什么呢?
首先需要注意的一点是,wait/notify方法必须存在于同步代码块里,因为这两个方法需要由锁对象去调用,在上面的例子中锁对象是list,调用wait与notify的方法都是同一个锁对象,具体用意在下面再讲。
然后我们来仔细说说代码的执行流程,首先我们先启动的是a线程,a线程拿到锁对象list,进入该线程run方法中的同步代码块,执行for循环,for循环中一直执行add添加操作,并进行if判断,当执行到第三次的时候进入if判断,执行list.wait(),当执行完这一句代码,当前线程立即释放list锁对象,由于此时,b线程也在等待同一把锁对象,所以该线程的拿到锁对象后执行对应的同步代码块内的内容。
当执行完list.notify()后,线程b会唤醒线程a,使其处于runnable状态,一旦抢到锁对象后立即接着list.wait()后的代码执行,与list.wait()不同的是,线程b在执行完list.notify()后没有立即释放锁,而是在执行完同步代码块中的所有内容后,锁才会被释放,锁被释放后,由于线程a已经被notify唤醒,所以会接着上次的地方执行。
在使用等待/唤醒机制的时候,有几个需要注意的地方。
- 一个notify()方法只会唤醒一个处于等待中的线程,要想唤醒所有,可以使用notifyAll()方法
- notify()方法只会唤醒被同一个锁对象wait()的线程。
对于第二点,如果我们将ThreadB修改成这样:
class ThreadB extends Thread{
private MyList list;
public ThreadB(MyList list) {
this.list = list;
}
@Override
public void run() {
synchronized (this) {
System.out.println("线程Thread检测到list中的长度变为3了,停止该线程");
this.notify();
System.out.println("notify方法调用结束");
}
}
}
执行结果如下,并且程序一直没有停止,可见线程a没有被唤醒。
第 0 此执行add操作
第 1 此执行add操作
第 2 此执行add操作
线程Thread检测到list中的长度变为3了,停止该线程
notify方法调用结束
这是因为调用notify与wait方法不是使用的同一个锁对象,可以理解为这两个notify和wait不是一对,所以没达到唤醒的目的。
总结
经过对比发现,我发现wait/notify机制更像是一种主动推送的实现,一旦A线程达到了某种状态,可以通过wait方法来通知与A线程使用同一个锁对象的线程,让他们去做相应的操作,由于只有一把锁,所以只能有一个线程获得执行机会,这个获得执行机会的线程肯定是在A线程中达到某种状态后才会执行,所以也就间接实现了通信的目的。
使用wait/notify实现线程间的通信的更多相关文章
- wait/notify实现线程间的通信
使线程之间进行通信之后,系统间的交互性更加强大,在大大提高CPU利用率的同时还会使程序对各线程任务在处理的过程中进行有效的把控与监督. 1.不使用wait/notify实现线程间通信 使用sleep( ...
- Java核心知识点学习----多线程并发之线程间的通信,notify,wait
1.需求: 子线程循环10次,主线程循环100次,这样间隔循环50次. 2.实现: package com.amos.concurrent; /** * @ClassName: ThreadSynch ...
- java多线程详解(6)-线程间的通信wait及notify方法
Java多线程间的通信 本文提纲 一. 线程的几种状态 二. 线程间的相互作用 三.实例代码分析 一. 线程的几种状态 线程有四种状态,任何一个线程肯定处于这四种状态中的一种:(1). 产生(New) ...
- Java 多线程(七) 线程间的通信——wait及notify方法
线程间的相互作用 线程间的相互作用:线程之间需要一些协调通信,来共同完成一件任务. Object类中相关的方法有两个notify方法和三个wait方法: http://docs.oracle.com/ ...
- Java-JUC(九):使用Lock替换synchronized,使用Condition的await,singal,singalall替换object的wait,notify,notifyall实现线程间的通信
Condition: condition接口描述了可能会与锁有关的条件变量.这些用法上与使用object.wait访问隐式监视器类似,但提供了更强大的功能.需要特别指出的是,单个lock可能与多个Co ...
- 并发编程系列小结(线程安全,synchronized,脏读,线程间的通信wait/notify,线程的三种实现方式Demo,可替代wait/notify的方法)
线程安全: 当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法就是线程安全的) synchronized: 可以在任意对象或方法上加锁,而加锁的这段代码称为 ...
- java线程基础巩固---线程间通信快速入门,使用wait和notify进行线程间的数据通信
之前已经对于线程同步相关的知识点进行了详细的学习,这次来学习一下线程间的通信相关的知识,话不多说直接用代码进行演练,以一个简陋的生产者消费者模型来初步了解下线程间通信是怎么一回事. 生产消费者第一版: ...
- Java多线程中线程间的通信
一.使用while方式来实现线程之间的通信 package com.ietree.multithread.sync; import java.util.ArrayList; import java.u ...
- 关于synchronized和lock 的使用及其在线程间的通信
题目要求:子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次 synchronized的使用 import java.util.conc ...
随机推荐
- smarty循环
1. 功能说明,在页面使用smarty循环100次输出,类似for循环100次{section name=total loop=100}{$smarty.section.total.index+1} ...
- node 设置自动启用定时任务控件 node-schedule
[转]Quartz中时间表达式的设置-----corn表达式 时间格式: <!-- s m h d m w(?) y(?) -->, 分别对应: 秒>分>小时>日&g ...
- 数据中台解析Hive SQL过程
一.数据中台解析SQL的目的: 数据中台需要对外提供数据特征查询的能力,因此中台查找并解析各个平台的sql,找出哪些表中的字段经常被使用,以便沉淀为特征,而我们要做的是找出sql中的数据表及其字段.以 ...
- 给WPF示例图形加上方便查看大小的格子
原文:给WPF示例图形加上方便查看大小的格子 有时,我们为了方便查看WPF图形的样式及比例等,需要一些辅助性的格线,置于图形.图像的背景中. 比如下图,就是为了更清晰地查看折线的图形,我们画了用于标示 ...
- WPF制作的党旗
原文:WPF制作的党旗 --------------------------------------------------------------------------------引用或转载时请保 ...
- python 教程 第一章、 简介
第一章. 简介 官方介绍: Python是一种简单易学,功能强大的编程语言,它有高效率的高层数据结构,简单而有效地实现面向对象编程.Python简洁的语法和对动态输入的支持,再加上解释性语言的本质,使 ...
- 傅里叶分析(matlab)
一维信号的傅里叶变换:fft(t) 二维图像的傅里叶变换:fft2(t) fft2(x) ⇒ fft(fft(x)')' 0. 基础 f(t)=∑k=−∞∞αkeikt 1. frequency sp ...
- ubuntu如何修改terminal终端的主机名(修改/etc/hostname文件)
有时候安装完Ubuntu系统后,打开命令终端,终端显示的主机名格式比较难看,例如 我最近买的国内某云的VPS. xxx@VM-1560-ubuntu$ xxx@VM-1560-ubuntu$ 对于有洁 ...
- [WPF] PerformClick ?
原文:[WPF] PerformClick ? [WPF] PerformClick ? 周银辉 WPF没有提供这个方法,还真是让人觉得有些讨厌啊.而关于这个嘛,Google中搜一下,一大堆,但一般 ...
- UVA - 825Walking on the Safe Side(dp)
id=19217">称号: UVA - 825Walking on the Safe Side(dp) 题目大意:给出一个n * m的矩阵.起点是1 * 1,终点是n * m.这个矩阵 ...