Java并发编程(九)线程间协作(下)
上篇我们讲了使用wait()和notify()使线程间实现合作,这种方式很直接也很灵活,但是使用之前需要获取对象的锁,notify()调用的次数如果小于等待线程的数量就会导致有的线程会一直等待下去。这篇我们讲多线程间接协作的方式,阻塞队列和管道通讯,间接协作的优点是使用起来更简单并且不易出错。
阻塞队列
阻塞队列提供了一种功能,即你可以在任何时刻向队列内扔一个对象,如果队列满了则当前线程阻塞;在任何时刻都可以从队列中取出一个对象,如果队列为空则当前线程阻塞。阻塞队列是线程安全的,使用它时无需加锁。此外其内部是使用显示锁实现的同步,使用Condition实现的线程阻塞。阻塞队列的接口是BlockingQueue,它有两个实现类:
1. ArrayBlockingQueue:底层使用数组实现的队列,有固定长度,调用其构造方法时必须提供队列的最大长度。
2. LinkedBlockingQueue:底层使用链表实现的队列,理论上讲是没有最大长度的,使用时不用提供队列长度;但实际上这个队列的长度不能超过Integer.MAX_VALUE。
这两个类使用的时候没有太大区别,我们以LinkedBlockingQueue为例,重写“学生去食堂打饭”的例子,代码如下:
class Student implements Runnable {
private Object wan = new Object();
public void run() {
try {
System.out.println("学生:取到了一个碗");
BlockingQueueTest.wanQueue.put(wan);
System.out.println("学生:阿姨帮忙盛饭");
wan = BlockingQueueTest.wanWithFanQueue.take();
System.out.println("学生:吃饭");
} catch (InterruptedException e) {}
}
}
class CafeteriaWorker implements Runnable {
public void run() {
try {
Object wan = BlockingQueueTest.wanQueue.take();
System.out.println("阿姨:给学生盛饭");
BlockingQueueTest.wanWithFanQueue.put(wan);
} catch (InterruptedException e) {}
}
}
public class BlockingQueueTest {
public static BlockingQueue wanQueue = new LinkedBlockingQueue();
public static BlockingQueue wanWithFanQueue = new LinkedBlockingQueue();
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Student());
exec.execute(new CafeteriaWorker());
exec.shutdown();
}
}
输出结果如下:
学生:取到了一个碗
学生:阿姨帮忙盛饭
阿姨:给学生盛饭
学生:吃饭
在这个例子中我们定义了两个队列,一个是空碗的队列,另一个是盛完饭的碗的队列。“学生线程”取到碗后将空碗放入wanQueue队列,然后试图从wanWithFanQueue队列中取出盛好的饭碗;“阿姨线程”试图从wanQueue队列中取出空碗,然后将盛好的饭碗放到wanWithFanQueue队列中。上次我们使用wait()方法时必须要求“阿姨线程”先启动,否则会导致“阿姨线程”错过学生的信号,而使用阻塞队列实现时我们就不再要求两个线程的启动顺序了,使用阻塞队列规避了错失信号的风险。有的同学可能会好奇为什么会使用两个队列,这是因为如果使用同一个队列,同学线程把碗扔进队列后,可能“阿姨线程”没来得及取出来就被“同学线程”拿回去了,感兴趣的同学可以自行测试。
管道通讯
通过管道的方式也可以使线程间实现交互,管道和阻塞队列类似,当管道内没有数据的时候,如果某个线程尝试去读取数据就会被阻塞。
我们可以使用PipedWriter和PipedReader来实现对管道数据的读取和写入。和阻塞队列不同的是,阻塞队列中不同线程都是操作一个队列的对象;使用管道时,不同的线程可以使用不同的对象,只要将它们注册为一个管道即可。
我们使用管道通信模拟一个线程对另一个线程表白,代码如下:
class Sender implements Runnable {
private PipedWriter writer;
Sender(PipedWriter writer) {
this.writer = writer;
}
public void run() {
String str1 = new String("I love you\n");
String str2 = new String("Do you love me\n");
try {
writer.write(str1.toCharArray());
writer.write(str2.toCharArray());
} catch (IOException e) {}
}
}
class Receiver implements Runnable {
private PipedReader reader;
public Receiver(PipedReader reader) {
this.reader = reader;
}
public void run() {
try {
while(true) {
char c = (char)reader.read();
System.out.print(c);
}
} catch (IOException e) {}
}
}
public class PipeCommunication {
public static void main(String[] args) throws Exception {
PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter(reader);
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Sender(writer));
exec.execute(new Receiver(reader));
Thread.sleep(1000);
exec.shutdownNow();
}
}
运行后输出结果如下,一秒后程序退出:
I love you
Do you love me
我们在主方法里先定义了一个PipedReader对象,然后将这个对象作为PipedWriter的构造方法的参数传给PipedWriter对象,这样就实现两个输入输出流的绑定,分别将两个流对象传给两个线程对象。在信息的接收方我们使用一个死循环让其不断的从管道内读入,从输出结果可以看出read()方法在管道内没有数据的时候被阻塞了,因为输出结果没有循环打印其它字符。此外主线程sleep一秒后调用了shutdownNow()方法,这个方法向所有运行着的线程发送中断信号,程序运行一秒后就退出了,我们可以看出中断信号打断了Receiver的阻塞状态,由此得出结论:管道类阻塞时可以被中断信号打断。
总结
本篇讲了使用阻塞队列和管道来实现线程间的合作,相对于使用wait()协作而言这两种方式更为高级,使用起来更容易而且不易错,此外阻塞队列和管道都是线程安全的,因此使用它们的时候不需要使用锁。需要实现线程间协作时可以根据实际需要,权衡利弊进行选择。
公众号:今日说码。关注我的公众号,可查看连载文章。遇到不理解的问题,直接在公众号留言即可。
Java并发编程(九)线程间协作(下)的更多相关文章
- Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- 19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- 【转】Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)
一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...
- Java多线程编程(6)--线程间通信(下)
因为本文的内容大部分是以生产者/消费者模式来进行讲解和举例的,所以在开始学习本文介绍的几种线程间的通信方式之前,我们先来熟悉一下生产者/消费者模式. 在实际的软件开发过程中,经常会碰到如下场景 ...
- Java并发编程:线程控制
在上一篇文章中(Java并发编程:线程的基本状态)我们介绍了线程状态的 5 种基本状态以及线程的声明周期.这篇文章将深入讲解Java如何对线程进行状态控制,比如:如何将一个线程从一个状态转到另一个状态 ...
- Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- Java并发编程:线程池的使用(转)
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- Java并发编程:线程池的使用(转载)
转载自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...
- Java并发编程:线程池的使用(转载)
文章出处:http://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...
随机推荐
- 利用open live writer工具的Metaweblog技术API接口同步到多个博客。
测试例子内容: hello world hello metaweblog hello open live writer
- forkjoin框架疑问记录
今天在看jdk1.7的forkjoin框架时候,里面有个例子如下: product类: public class Product { private String name; private doub ...
- Exchange2016 & Skype for business 集成之一配置合作伙伴应用程序
准备条件 为Skype for Business Server和Exchange Server之间建立服务器到服务器的身份验证,您必须做两件事:1)您必须为每台服务器分配合适的证书(详细参考文档htt ...
- Exchange Server 2016 管理邮箱收发限制
备注:本文是Exchange Server 2016管理系列的配套课件,更加详细的讲解请参考视频课程,文章结尾有视频课程主页的链接. 进行收发邮件大小的限制是很有必要的,因为邮件服务器不能当作文件服务 ...
- 转 linux设置开机服务自动启动/关闭自动启动命令 chkconfig
原文连接http://blog.csdn.net/jiangguilong2000/article/details/8259360 chkconfig命令主要用来更新(启动或停止)和查询系统服务的运行 ...
- Angular2 constructor VS ngOnInit
constructor和ngOnInit钩子有什么不同? constructor constructor(构造函数)是ES6类或TypeScript类中的特殊方法,而不是Angular的方法,主要用来 ...
- 在Django中使用Q()对象
转载于: http://www.smallerpig.com/1000.html 问题 一般我们在Django程序中查询数据库操作都是在QuerySet里进行进行,例如下面代码: >>& ...
- Input and Output-The input is all the sources of action for your app
Programs take input and produce output. The output is the result of doing something with the input. ...
- [TJOI2018]智力竞赛
题目 发现我们需要最大化最小值,基本是二分了 那么我们二分出来一个值我们将小于等于这个值的都删去,现在的问题变成了如何用\(n+1\)条路径覆盖这张图 这不最小路径覆盖吗 于是我就忘了最小路径覆盖怎么 ...
- 【CSS】iconfont的使用
说到浏览器对@font-face的兼容问题,这里涉及到一个字体format的问题,因为不同的浏览器对字体格式支持是不一致的,这样大家有必要了解一下,各种版本的浏览器支持什么样的字体,前面也简单带到了有 ...