java多线程回顾4:线程通信
1、线程的协调运行
线程的协调运行有一个经典案例,即生产者和消费者问题。
假设有一个货架,生产者往货架上放货物,消费者从货架上取货物。
为了方便讲解,制定一个规则,生产者每放上一个货物,消费者就得取走一个货物。不允许连续放两次,也不允许连续取两次。
为了实现这个功能,可以使用wait()、notify()和notifyAll()三个方法。注意,这三个方法不属于Thread类,而是属于Object类,且必须由同步监视器对象调用。详细用法如下:
<![if !supportLists]>Ø <![endif]>对于使用synchronized修饰的同步方法,因为默认实例(this)就是同步监视器对象,所以可以在同步方法中直接调用这三个方法。
<![if !supportLists]>Ø <![endif]>对于使用synchronized修饰的同步代码块,同步监视器对象是synchronized后括号里的对象,必须使用该对象调用这三个方法
这三个方法的作用如下:
<![if !supportLists]>Ø <![endif]>wait():使当前线程等待,直到其他线程调用该同步监视器的notify()或notifyAll()来唤醒该线程。调用wait()的当前线程会释放对该同步监视器的锁定。
<![if !supportLists]>Ø <![endif]>notify():唤醒在此同步监视器上的单个线程,如果有多个线程在等待,则随机唤醒一个。被唤醒的线程会在当前线程释放对同步监视器的锁定后开始运行。
<![if !supportLists]>Ø <![endif]>notifyAll():唤醒在此同步监视器上的所有线程,其余和notify()相同。
货架代码如下:
publicclass Shelf {
//是否有货物的标志,true:有;false:没有
privatebooleanflag;
//放入货物的方法
publicsynchronizedvoid product(){
//flag为假时表示没有货物,此时可放入货物
if(!flag){
flag = true;
System.out.println(Thread.currentThread().getName()+"放入了货物。");
notifyAll();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//取走货物的方法
publicsynchronizedvoid custom(){
//flag为真时表示有货物,此时可取走货物
if (flag) {
flag = false;
System.out.println(Thread.currentThread().getName()+"取走了货物。");
notifyAll();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生产者线程代码如下:
publicclass ProducerThread implements Runnable{
private Shelf shelf;
public ProducerThread(Shelf shelf){
this.shelf = shelf;
}
@Override
publicvoid run() {
//放十次货物
for (int i = 0; i < 10; i++) {
shelf.product();
}
}
}
消费者线程代码如下:
publicclass CustomerThread implements Runnable{
private Shelf shelf;
public CustomerThread(Shelf shelf){
this.shelf = shelf;
}
@Override
publicvoid run() {
//取十次货物
for (int i = 0; i < 10; i++) {
shelf.custom();
}
}
}
测试代码如下:
publicclass TestShelf {
publicstaticvoid main(String[] args) {
Shelf shelf = new Shelf();
ProducerThread producerThread = new ProducerThread(shelf);
CustomerThread customerThread = new CustomerThread(shelf);
new Thread(producerThread,"生产者1").start();
new Thread(producerThread,"生产者2").start();
new Thread(customerThread,"消费者").start();
}
}
运行结果如下:
生产者1放入了货物。
消费者取走了货物。
生产者1放入了货物。
消费者取走了货物。
生产者2放入了货物。
消费者取走了货物。
生产者1放入了货物。
消费者取走了货物。
生产者1放入了货物。
消费者取走了货物。
生产者2放入了货物。
消费者取走了货物。
生产者2放入了货物。
消费者取走了货物。
生产者2放入了货物。
上例中启动了两个生产者一个消费者,可见生产和消费是交替进行的,满足生产者每放上一个货物,消费者就得取走一个货物的规则。
最后程序将阻塞,因为消费者比较少,生产者最后只能一直等消费者来取走货物。同时消费者取货物的次数也小于10次,因为有时取货时没货了。
2、使用条件变量协调运行
如果不使用synchronized,而是使用Lock对象来保证同步,则不能使用wait()、notify()和notifyAll()这三个方法。此时可使用Condition类来实现协作。
Condition实例必须从Lock实例中获得,也有三个方法,如下:
<![if !supportLists]>Ø <![endif]>await():和wait()类似,使当前线程等待,直到被唤醒。相比wait(),await()有更多的变体,这个以后再说。
<![if !supportLists]>Ø <![endif]>signal():和notify()类似,唤醒当前Lock对象上的单条线程。
<![if !supportLists]>Ø <![endif]>signalAll()和notifyAll()类似,唤醒当前Lock对象上的所有线程。
生产者线程、消费者线程、测试代码和测试结果的代码跟上一节完全一样,这里只放出货架的代码。
货架的代码:
publicclass Shelf {
//是否有货物的标志,true:有;false:没有
privatebooleanflag;
//定义Lock对象
privatefinal Lock lock = new ReentrantLock();
//获取Lock对象对应的Condition对象
privatefinal Condition condition= lock.newCondition();
//放入货物的方法
publicvoid product(){
lock.lock();
try {
//flag为假时表示没有货物,此时可放入货物
if(!flag){
flag = true;
System.out.println(Thread.currentThread().getName()+"放入了货物。");
condition.signalAll();
}else{
condition.await();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
//取走货物的方法
publicsynchronizedvoid custom(){
lock.lock();
try {
//flag为真时表示有货物,此时可取走货物
if (flag) {
flag = false;
System.out.println(Thread.currentThread().getName()+"取走了货物。");
condition.signalAll();
}else{
condition.await();
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
3、使用管道流通信
线程之间使用管道流通信并不是最好的方式,通常推荐使用共享资源的方式交换信息。所以本节内容了解一下就好。
管道流有三种形式,分别是管道字节流(PipedInputStream、PipedOutputStream)、管道字符流(PipedReader、PipedWriter)和新IO的管道Channel(Pipe.SinkChannel、Pipe.SourceChannel)。
使用步骤如下:
<![if !supportLists]>Ø <![endif]>创建输入管道输入流和管道输出流对象。
<![if !supportLists]>Ø <![endif]>使用管道输入流或管道输出流的connect方法把输入流和输出流连接起来。
<![if !supportLists]>Ø <![endif]>将管道输入流、管道输出流分别传入两个线程
<![if !supportLists]>Ø <![endif]>线程使用各自的管道流通信
代码就不贴了
java多线程回顾4:线程通信的更多相关文章
- java多线程 -- Condition 控制线程通信
Api文档如此定义: Condition 将 Object 监视器方法(wait.notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对 ...
- java 多线程 day13 condition 线程通信
/** * Created by chengtao on 17/12/5. * Condition 类似 wait和notify,解决线程间的同步问题 */ import java.util.conc ...
- Java多线程——进程和线程
Java多线程——进程和线程 摘要:本文主要解释在Java这门编程语言中,什么是进程,什么是线程,以及二者之间的关系. 部分内容来自以下博客: https://www.cnblogs.com/dolp ...
- Java多线程系列--“JUC线程池”06之 Callable和Future
概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...
- Java多线程系列--“JUC线程池”02之 线程池原理(一)
概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- Java多线程系列--“JUC线程池”04之 线程池原理(三)
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...
- Java多线程系列--“JUC线程池”05之 线程池原理(四)
概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...
- -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中
本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait( ...
- 转:java多线程CountDownLatch及线程池ThreadPoolExecutor/ExecutorService使用示例
java多线程CountDownLatch及线程池ThreadPoolExecutor/ExecutorService使用示例 1.CountDownLatch:一个同步工具类,它允许一个或多个线程一 ...
随机推荐
- [BZOJ1694/1742/3074]The Cow Run 三倍经验
Description John养了一只叫Joseph的奶牛.一次她去放牛,来到一个非常长的一片地,上面有N块地方长了茂盛的草.我们可 以认为草地是一个数轴上的一些点.Joseph看到这些草非常兴奋, ...
- Cocos2d-x 学习笔记(20) ControlButton
[Cocos2d-x 学习笔记 目录链接] 1. 简介 ControlButton实现了按钮功能,根据触摸的位置和移动的过程可识别9中EventType类型,执行对应的回调函数. 直接继承了Contr ...
- opencv::点多边形测试
点多边形测试 测试一个点是否在给定的多边形内部,边缘或者外部 double pointPolygonTest( InputArray contour, // 输入的轮廓 Point2f pt, // ...
- 『嗨威说』算法设计与分析 - 动态规划思想小结(HDU 4283 You Are the One)
本文索引目录: 一.动态规划的基本思想 二.数字三角形.最大子段和(PTA)递归方程 三.一道区间动态规划题点拨升华动态规划思想 四.结对编程情况 一.动态规划的基本思想: 1.1 基本概念: 动态规 ...
- unity www下载导致内存占用增加问题
服务端或者数据库更改导致客户端更改,最合理的处理方法是客户端时刻检测版本号(可以通过实时检测版本号),如果实时刷新数据库的数据开销比较大,尤其是有图片元素时. 采用unity www类下载时,虽然结束 ...
- docker-compose下的java应用启动顺序两部曲之一:问题分析
在docker-compose编排多个容器时,需要按实际情况控制各容器的启动顺序,本文是<docker-compose下的java应用启动顺序两部曲>的第一篇,文中会分析启动顺序的重要性, ...
- fenby C语言 P6
printf=格式输出函数; printf=("两个相加的数字是:%d,%d,他们的和是:%d\n",a,b,c); %d整数方式输出; \n=Enter; int a=1; fl ...
- Dubbo配合SpringBoot,实现接口多个实现(group)
SpringBoot配合Dubbo,使用@Service和@Reference,group实现接口多实现 公司项目升级,需要实现springBoot + Dubbo,并支持一个接口多个实现的情况.遇到 ...
- ASP.NET Core如何使用压缩中间件提高Web应用程序性能
前言 压缩可以大大的降低我们Web服务器的响应速度,压缩从而提高我们网页的加载速度,以及节省一定的带宽. 何时使用相应压缩中间件 在IIS,Apache,Nginx中使用基于服务端的响应压缩技术.中间 ...
- Windows包管理器
Windows包管理器 Scoop 参考 安装命令 set-executionpolicy remotesigned -scope currentuser #用powershell执行 iex (ne ...