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并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...
随机推荐
- Mac怎么生成.ssh文件
可使用如下命令生成 1 ssh-keygen -t rsa 因为mac系统也是从unix基础上演变过来的,所以很多核心的东西也是与unix相通的. 1.-t 是指定加密参数为ras,默认是dsa 2. ...
- Bitmap到底占多少内存
转至:Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存? Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟 ...
- Qt消息机制和事件、事件过滤
一,事件 事件(event)是由系统或者 Qt 本身在不同的时刻发出的.当用户按下鼠标.敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件.一些事件在对用户操作做出响应时发出,如键盘事件等 ...
- web测试流程的总结及关注点
项目的测试流程大只包含的几个阶段:立项.需求评审.用例评审.测试执行.测试报告文档 一.立项后测试需要拿到的文档 1.需求说明书 2.原型图(及UI图) 3.接口文档 4.数据库字典(表的数量.缓存机 ...
- MySQL order null 0 - 把null和0(零)排在最后
1.一般的order by 语句其返回的结果为 SELECT `vcenter_ip`, `status`, `sla_id` FROM vm_list ORDER BY sla_id ASC; 2. ...
- RequestMapping 支持的方法
基于版本:Spring Framework 4.3.5.RELEASE 1.支持指定多个方法 @RequestMapping(value = "/index", method = ...
- google学习
https://developers.google.com/machine-learning/crash-course/ https://developers.google.com/machine-l ...
- security权限控制
目录 前言 数据库和dimain 静态页面 配置文件 web.xml引入 service校验方法 用户名的获取 不同角色访问控制权限 jsp页面 后台 前言 spring自带角色权限控制框架 用户-角 ...
- IPv4地址结构体sockaddr_in详解
sockaddr_in结构体定义 struct sockaddr_in { sa_family_t sin_family; //地址族(Address Family) uint16_t sin_por ...
- Selenium2+python自动化
一.打开网站1.第一步:从selenium里面导入webdriver模块2.打开Firefox浏览器(Ie和Chrome对应下面的)3.打开百度网址二.设置休眠1.由于打开百度网址后,页面加载需要几秒 ...