java多线程系列(三)---等待通知机制
等待通知机制
前言:本系列将从零开始讲解java多线程相关的技术,内容参考于《java多线程核心技术》与《java并发编程实战》等相关资料,希望站在巨人的肩膀上,再通过我的理解能让知识更加简单易懂。
目录
- 认识cpu、核心与线程
- java多线程系列(一)之java多线程技能
- java多线程系列(二)之对象变量的并发访问
- java多线程系列(三)之等待通知机制
- java多线程系列(四)之ReentrantLock的使用
- java多线程系列(五)之synchronized ReentrantLock volatile Atomic 原理分析
- java多线程系列(六)之线程池原理及其使用
非等待通知
public void run() {
try {
for (int i = 0; i < 10; i++) {
list.add();
System.out.println("添加了" + (i + 1) + "个元素");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run() {
try {
while (true) {
if (list.size() == 5) {
System.out.println("==5了,线程b要退出了!");
throw new InterruptedException();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- 两个线程实现了通信,但list大小为5的时候,线程B退出了,但是线程B不停地轮询是否为5,这个时候是很占资源的
- 如果轮询的时间间隔小,这个时候更加浪费资源
- 如果轮询的时间间隔大,那么还可能错过了想要的数据,比如可能错过了5
- 这里共享了list,所以实现了通信,但是因为不知道什么时候通信,所以不停地轮询,这种通信有缺点,一是浪费cpu资源,二是可能读取到错误的数据
什么是等待通知机制
- 线程A要等待线程B发出通知才执行,这个时候线程A可以执行wait方法,等待线程B执行notify方法唤醒线程A
等待通知机制实现
public void run() {
try {
synchronized (lock) {
if (MyList.size() != 5) {
System.out.println("wait begin "
+ System.currentTimeMillis());
lock.wait();
System.out.println("wait end "
+ System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run() {
try {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
MyList.add();
if (MyList.size() == 5) {
lock.notify();
System.out.println("已发出通知!");
}
System.out.println("添加了" + (i + 1) + "个元素!");
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- 将上面的代码进行更改,当大小不等于5的时候,线程A处于wait状态,直到线程B发出通知,唤醒线程A,通过等待通知机制,避免了线程A不停轮询造成的资源浪费
消息通知机制注意点
- wait和notify必须是在同步方法和同步代码块里面调用,要不然会抛出异常
- notify方法是继承自Object类,可以唤醒在此对象监视器等待的线程,也就是说唤醒的是同一个锁的线程
- notify方法调用之后,不会马上释放锁,而是运行完该同步方法或者是运行完该同步代码块的代码
- 调用notify后随机唤醒的是一个线程
- 调用wait方法后会将锁释放
- wait状态下中断线程会抛出异常
- wait(long),超过设置的时间后会自动唤醒,还没超过该时间也可以通过其他线程唤醒
- notifyAll可以唤醒同一锁的所有线程
- 如果线程还没有处于等待状态,其他线程进行唤醒,那么不会起作用,此时会打乱程序的正常逻辑
案例:生产者消费者模式
一个生产者,一个消费者
public void setValue() {
try {
synchronized (lock) {
if (!ValueObject.value.equals("")) {
lock.wait();
}
String value = System.currentTimeMillis() + "_"
+ System.nanoTime();
System.out.println("set"+ value);
ValueObject.value = value;
lock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void getValue() {
try {
synchronized (lock) {
if (ValueObject.value.equals("")) {
lock.wait();
}
System.out.println("get"+ ValueObject.value);
ValueObject.value = "";
lock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run() {
while (true) {
r.getValue();
}
}
public void run() {
while (true) {
p.setValue();
}
}
- 如果我们创建一个生产线程,一个消费线程,那么这个时候会交替运行
多个生产者,多个消费者
public void getValue() {
try {
synchronized (lock) {
while (ValueObject.value.equals("")) {
System.out.println("消费者 "
+ Thread.currentThread().getName() + " WAITING了☆");
lock.wait();
}
System.out.println("消费者 " + Thread.currentThread().getName()
+ " RUNNABLE了");
ValueObject.value = "";
lock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run() {
while (true) {
r.getValue();
}
}
public void setValue() {
try {
synchronized (lock) {
while (!ValueObject.value.equals("")) {
System.out.println("生产者 "
+ Thread.currentThread().getName() + " WAITING了★");
lock.wait();
}
System.out.println("生产者 " + Thread.currentThread().getName()
+ " RUNNABLE了");
String value = System.currentTimeMillis() + "_"
+ System.nanoTime();
ValueObject.value = value;
lock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run() {
while (true) {
p.setValue();
}
}
- 如果这个时候创建多个生产者,多个消费者,如果连续唤醒的是同类线程,那么会出现假死状态,就是线程都处于waiting状态,因为notify随机唤醒一个线程,如果唤醒的同类的,那么就浪费了一次唤醒,如果这个时候无法再唤醒异类线程,那么就会假死。这种情况把notify改成notifyAll()就行了。
消息通知机制需要注意的地方
- 是否线程唤醒的是同类线程会造成影响
- 生产者消费模式,判断条件if和while应该使用哪一个
通过管道进行线程间通信
public class ThreadWrite extends Thread {
private WriteData write;
private PipedOutputStream out;
public ThreadWrite(WriteData write, PipedOutputStream out) {
super();
this.write = write;
this.out = out;
}
@Override
public void run() {
write.writeMethod(out);
}
}
public class ThreadRead extends Thread {
private ReadData read;
private PipedInputStream input;
public ThreadRead(ReadData read, PipedInputStream input) {
super();
this.read = read;
this.input = input;
}
@Override
public void run() {
read.readMethod(input);
}
}
public class Run {
public static void main(String[] args) {
try {
WriteData writeData = new WriteData();
ReadData readData = new ReadData();
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream();
// inputStream.connect(outputStream);
outputStream.connect(inputStream);//关键
ThreadRead threadRead = new ThreadRead(readData, inputStream);
threadRead.start();
Thread.sleep(2000);
ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
threadWrite.start();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- PipedInputStream和PiepedOutputStream(对应字符流PipedReader和PipedOutputWriter)这几个类可以实现线程间流的通信,将管道输出流和输出流连接,实现一个线程往管道发送数据,一个线程从管道读取数据
join方法
public static void main(String[] args) {
try {
MyThread threadTest = new MyThread();
threadTest.start();
threadTest.join();
System.out.println("threadTest对象执行完,我再执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- 当前线程阻塞(main线程),调用线程(threadTest)正常执行,执行完后当前线程(main)继续执行
public class ThreadB extends Thread {
@Override
public void run() {
try {
ThreadA a = new ThreadA();
a.start();
a.join();
System.out.println("线程B在run end处打印了");
} catch (InterruptedException e) {
System.out.println("线程B在catch处打印了");
e.printStackTrace();
}
}
}
- 如果线程B执行完了join方法,此时线程B被中断,那么这个时候抛出异常,但是线程A正常运行
join(long)和sleep(long)的区别
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
- 从join方法的源代码可以发现,他的核心方法是wait,在前面已经提到wait方法会释放锁,说明join方法也会释放锁,但是sleep是不会释放锁的。
- join方法是非静态的,而sleep是静态的
ThreadLocal
- 解决变量在各个线程的隔离性,每个线程绑定自己的值
public void run() {
try {
for (int i = 0; i < 100; i++) {
if (Tools.tl.get() == null) {
Tools.tl.set("ThreadA" + (i + 1));
} else {
System.out.println("ThreadA get Value=" + Tools.tl.get());
}
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run() {
try {
for (int i = 0; i < 100; i++) {
if (Tools.tl.get() == null) {
Tools.tl.set("ThreadB" + (i + 1));
} else {
System.out.println("ThreadB get Value=" + Tools.tl.get());
}
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public class Tools {
public static ThreadLocal tl = new ThreadLocal();
}
- 每个线程都设置了值,但是得到的值却是自己的,互相隔离
- 如果不开始不设置值,那么得到的值都是null,可以通过继承ThreadLocal,重载initalValue方法,设置初始值
public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
- InheritableThreadLocal,子线程可以继承父线程的值
public class InheritableThreadLocalExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
System.out.println(" 在Main线程中取值=" + Tools.tl.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//main线程和A线程输出的一样
- 在上面代码的基础上,重写childValue方法可以设置子线程的值
我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)
作者:jiajun 出处: http://www.cnblogs.com/-new/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。
java多线程系列(三)---等待通知机制的更多相关文章
- Java Concurrency - wait & notify, 等待通知机制
生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空.大小为 N 的缓冲区.只有当缓冲区没满的时候,生产者才能把消息放入缓冲区,否则必须等待:只有缓冲区不空的 ...
- Java 线程间通信 —— 等待 / 通知机制
本文部分摘自<Java 并发编程的艺术> volatile 和 synchronize 关键字 每个处于运行状态的线程,如果仅仅是孤立地运行,那么它产生的作用很小,如果多个线程能够相互配合 ...
- JMM之Java线程间通讯——等待通知机制及其经典范式
在并发编程中,实际处理涉及两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体). 通信是指线程之间以何种机制来交换信息.在共享内存的并发模型里,线程之间共享程序的公共状 ...
- (Java多线程系列三)线程间通讯
Java多线程间通讯 多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同. 1.使用wait()和notify()方法在线程中通讯 需求:第一个线程写入(input)用户,另一个线程 ...
- Java多线程系列三——实现线程同步的方法
两种实现线程同步的方法 方法 特性 synchronized 不需要显式地加解锁,易实现 ReentrantLock 需要显式地加解锁,灵活性更好,性能更优秀,结合Condition可实现多种条件锁 ...
- 【Java多线程系列三】实现线程同步的方法
两种实现线程同步的方法 方法 特性 synchronized 不需要显式的加锁,易实现 ReentrantLock 需要显式地加解锁,灵活性更好,性能更优秀,结合Condition可实现多种条件锁 ...
- 【Java并发基础】使用“等待—通知”机制优化死锁中占用且等待解决方案
前言 在前篇介绍死锁的文章中,我们破坏等待占用且等待条件时,用了一个死循环来获取两个账本对象. // 一次性申请转出账户和转入账户,直到成功 while(!actr.apply(this, targe ...
- java并发编程实战《六》等待-通知机制
用"等待-通知"机制优化循环等待 前言 在破坏占用且等待条件的时候,如果转出账本和转入账本不满足同时在文件架上这个条件,就用死循环的方式来循环等待. 1 // 一次性申请转出账户和 ...
- Java多线程系列十——BlockingQueue
参考资料:http://ifeve.com/java-synchronousqueue/http://www.cnblogs.com/jackyuj/archive/2010/11/24/188655 ...
随机推荐
- 抓包工具 - HttpWatch
HttpWatch是功能强大的网页数据分析工具,集成在IE工具栏,主要功能有网页摘要.cookies管理.缓存管理.消息头发送/接收,字符查询.POST数据.目录管理功能和报告输出.HttpWatch ...
- WPF界面XAML中的if……else……
xaml本身并不支持if--else--,要用Converter替代if--else--来实现我们想要的效果,知者请速离开,不要浪费时间 需求:按照Window的WindowState来决定Gri ...
- jsonp跨域再谈
昨天面试雷锋网,问到了jsonp跨域的问题,其实这个问题之前就会的,没有多大的深入,记得有一个名词在跨域中出现,就是同源机制, JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在J ...
- 转换String三种方式比较:toString()、String.valueOf()、(String)
简单介绍: 1.toString,需要保证调用这个方法的类.方法.变量不为null,否则会报空指针. 2.String.valueOf.这个方法在使用的时候是有些特殊的.一般情况下,如果是确定类型的n ...
- JS 点击复制按钮 将文字复制到手机剪贴板
我们在制作移动端网页的时候,经常会遇到这样一个问题,如何点击一个"复制"按钮,把一串文字复制到手机剪贴板,如上图所示. 看了网上的一些方法后,感觉那些方法都太复杂,有点要用插件,有 ...
- windows利用iis配置反向代理实现ECS内网互通oss
IIS实现反向代理 新建两个站点,端口分别使用 80 和 81,在DNS中新建A记录,指向该计算机(10.4.34.41) 配置过程如下: 1.在Windows Server 2012 R2上 安装A ...
- 一道C语言安全编码题目
1.前言 最近在网上看到一道C语言题目,用C语言实现一个函数,给定一个int类型的整数,函数输出逆序的整数,例如输入123,则输出字符串"321",,输入-123,则输出字符串&q ...
- ViewPager实现无限轮播踩坑记
最近笔者想通过ViewPager来实现一个广告Banner,并实现无限轮播的效果,但是在这个过程中踩了不少的坑,听我慢慢道来.如果大家有遇到和我一样的情况,可以参考我的解决方法,没有那就更好,如果针对 ...
- JAVA基础-JSON
/** * 1.根据接收到的JSON字符串来解析字符串中所包含的数据和数据对象 */ System.out.println("1.根据接收到的JSON字符串来解析字符串中所包含的数据和数据对 ...
- python多线程几种方法实现
python多线程编程 Python多线程编程中常用方法: 1.join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成操作后才能执行,那么在调用线程的时就可以使用被调线程 ...