最近一直在看《Think In Java》里关于并发部分的章节,读到第二十一章有一个有趣的比喻:必须先挖房子的地基,但是接下来可以并行的铺设钢结构和构建水泥部件,而这两项任务必须在混凝土浇筑之前完成。管道必须在水泥板浇注之前到位,而水泥板必须在开始构筑房屋骨架之前到位。

在这些任务中,某些可以并行执行,但是某些步骤需要所有的任务都结束之后才能开动,这是线程之间协作的必要性。

在此之前,我们学习过使用notify()、notifyAll()和wait()来控制线程间的协作,让我们先来回顾一下。notify()、notifyAll()和wait()这三个方法同属于Object对象,wait()会使得当前线程等待并交出对象的锁,直到别的线程调用notify()或notifyAll()后可能会被唤醒。

对于一些简单的问题,这已经够用了,但是Java SE5中的concurrent包中提供了BlockingQueue、Condition等类来帮助我们完成更复杂的线程间协作的任务。

下面看一个例子,一台机器具有三个任务:一个制作吐司、一个给吐司抹黄油,另一个在抹过黄油的吐司上涂果酱。通过各个处理过程之间的BlockingQueue来运行这个程序。来自Think In Java (p.s. 我觉得这本书难懂的原因,在于你在理解它教导并发概念的同时,还得十分小心地注意其余的语法细节,一定要有耐心!)。

package concurrency;//: concurrency/ToastOMatic.java
// A toaster that uses queues.
import java.util.concurrent.*;
import java.util.*;
import static net.mindview.utill.Print.*; class Toast {
public enum Status { DRY, BUTTERED, JAMMED }
private Status status = Status.DRY;
private final int id;
public Toast(int idn) { id = idn; }
public void butter() { status = Status.BUTTERED; }
public void jam() { status = Status.JAMMED; }
public Status getStatus() { return status; }
public int getId() { return id; }
public String toString() {
return "Toast " + id + ": " + status;
}
} class ToastQueue extends LinkedBlockingQueue<Toast> {} class Toaster implements Runnable {
private ToastQueue toastQueue;
private int count = 0;
private Random rand = new Random(47);
public Toaster(ToastQueue tq) { toastQueue = tq; }
public void run() {
try {
while(!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(
100 + rand.nextInt(500));
// Make toast
Toast t = new Toast(count++);
print(t);
// Insert into queue
toastQueue.put(t);
}
} catch(InterruptedException e) {
print("Toaster interrupted");
}
print("Toaster off");
}
} // Apply butter to toast:
class Butterer implements Runnable {
private ToastQueue dryQueue, butteredQueue;
public Butterer(ToastQueue dry, ToastQueue buttered) {
dryQueue = dry;
butteredQueue = buttered;
}
public void run() {
try {
while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = dryQueue.take();
t.butter();
print(t);
butteredQueue.put(t);
}
} catch(InterruptedException e) {
print("Butterer interrupted");
}
print("Butterer off");
}
} // Apply jam to buttered toast:
class Jammer implements Runnable {
private ToastQueue butteredQueue, finishedQueue;
public Jammer(ToastQueue buttered, ToastQueue finished) {
butteredQueue = buttered;
finishedQueue = finished;
}
public void run() {
try {
while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = butteredQueue.take();
t.jam();
print(t);
finishedQueue.put(t);
}
} catch(InterruptedException e) {
print("Jammer interrupted");
}
print("Jammer off");
}
} // Consume the toast:
class Eater implements Runnable {
private ToastQueue finishedQueue;
private int counter = 0;
public Eater(ToastQueue finished) {
finishedQueue = finished;
}
public void run() {
try {
while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = finishedQueue.take();
// Verify that the toast is coming in order,
// and that all pieces are getting jammed:
if(t.getId() != counter++ ||
t.getStatus() != Toast.Status.JAMMED) {
print(">>>> Error: " + t);
System.exit(1);
} else
print("Chomp! " + t);
}
} catch(InterruptedException e) {
print("Eater interrupted");
}
print("Eater off");
}
} public class ToastOMatic {
public static void main(String[] args) throws Exception {
ToastQueue dryQueue = new ToastQueue(),
butteredQueue = new ToastQueue(),
finishedQueue = new ToastQueue();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Toaster(dryQueue));
exec.execute(new Butterer(dryQueue, butteredQueue));
exec.execute(new Jammer(butteredQueue, finishedQueue));
exec.execute(new Eater(finishedQueue));
TimeUnit.SECONDS.sleep(5);
exec.shutdownNow();
}
} /* (Execute to see output) *///:~

看完晕乎乎的?很正常,所以才需要我来给大家讲解啦 :)

首先可以注意到的,程序中并没有出现任何Lock对象或是synchronized关键字来同步,这是因为在实现BlockingQueue的队列类内部已经使用Condition在维护。这降低了程序的耦合度,使得每个类只需要和自己的BlockingQueue通信。

程序中定义了:

一个实体类:Toast。使用enum来管理状态是一个优秀的示例。

三个队列:dryQueue、butteredQueue、finishedQueue

四个Runnable任务:Toaster、Butterer、Jammer、Eater

根据字面意思理解,当线程不被中断的时候,Toaster负责制作吐司,所以只需要和dryQueue通信。Butterer在吐司上涂黄油,需要从dryQueue中取出原味土司,涂上黄油(t.butter())后放入butteredQueue。Jammer在抹过黄油的吐司上涂果酱,需要从butteredQueue中取出,涂上果酱后放入finishedQueue。Eater就只需要从finishedQueue中取出来吃啦。细心的读者还会发现Eater中做了检查,如果不是涂上果酱的吐司就不吃(傲娇的表情)。如果线程被中断,任务就打印信息并退出。

TimeUnit.SECONDS.sleep(3)的作用是当前线程等待3秒,等待后台制作吐司。exec.shutdownNow()停止当前线程池。

是不是觉得自己理解了?那么还有一道课后题留给大家:修改ToastOMatic.java,使用两个单独的组装线来创建涂有黄油和果酱的三明治(即不必先涂黄油再涂果酱,可以异步处理,明显提高工作效率)。

答案在这里:

//: concurrency/E29_ToastOMatic2.java
/********************** Exercise 29 ***********************
* Modify ToastOMatic.java to create peanut butter and jelly
* on toast sandwiches using two separate assembly lines
* (one for peanut butter, the second for jelly, then
* merging the two lines).
*********************************************************/
package concurrency;
import java.util.concurrent.*;
import java.util.*;
import static net.mindview.utill.Print.*; class Toast {
public enum Status {
DRY,
BUTTERED,
JAMMED,
READY {
public String toString() {
return
BUTTERED.toString() + " & " + JAMMED.toString();
}
}
}
private Status status = Status.DRY;
private final int id;
public Toast(int idn) { id = idn; }
public void butter() {
status =
(status == Status.DRY) ? Status.BUTTERED :
Status.READY;
}
public void jam() {
status =
(status == Status.DRY) ? Status.JAMMED :
Status.READY;
}
public Status getStatus() { return status; }
public int getId() { return id; }
public String toString() {
return "Toast " + id + ": " + status;
}
} class ToastQueue extends LinkedBlockingQueue<Toast> {} class Toaster implements Runnable {
private ToastQueue toastQueue;
private int count;
private Random rand = new Random(47);
public Toaster(ToastQueue tq) { toastQueue = tq; }
public void run() {
try {
while(!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(
100 + rand.nextInt(500));
// Make toast
Toast t = new Toast(count++);
print(t);
// Insert into queue
toastQueue.put(t);
}
} catch(InterruptedException e) {
print("Toaster interrupted");
}
print("Toaster off");
}
} // Apply butter to toast:
class Butterer implements Runnable {
private ToastQueue inQueue, butteredQueue;
public Butterer(ToastQueue in, ToastQueue buttered) {
inQueue = in;
butteredQueue = buttered;
}
public void run() {
try {
while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = inQueue.take();
t.butter();
print(t);
butteredQueue.put(t);
}
} catch(InterruptedException e) {
print("Butterer interrupted");
}
print("Butterer off");
}
} // Apply jam to toast:
class Jammer implements Runnable {
private ToastQueue inQueue, jammedQueue;
public Jammer(ToastQueue in, ToastQueue jammed) {
inQueue = in;
jammedQueue = jammed;
}
public void run() {
try {
while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = inQueue.take();
t.jam();
print(t);
jammedQueue.put(t);
}
} catch(InterruptedException e) {
print("Jammer interrupted");
}
print("Jammer off");
}
} // Consume the toast:
class Eater implements Runnable {
private ToastQueue finishedQueue;
public Eater(ToastQueue finished) {
finishedQueue = finished;
}
public void run() {
try {
while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = finishedQueue.take();
// Verify that all pieces are ready for consumption:
if(t.getStatus() != Toast.Status.READY) {
print(">>>> Error: " + t);
System.exit(1);
} else
print("Chomp! " + t);
}
} catch(InterruptedException e) {
print("Eater interrupted");
}
print("Eater off");
}
} // Outputs alternate inputs on alternate channels:
class Alternator implements Runnable {
private ToastQueue inQueue, out1Queue, out2Queue;
private boolean outTo2; // control alternation
public Alternator(ToastQueue in, ToastQueue out1,
ToastQueue out2) {
inQueue = in;
out1Queue = out1;
out2Queue = out2;
}
public void run() {
try {
while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = inQueue.take();
if(!outTo2)
out1Queue.put(t);
else
out2Queue.put(t);
outTo2 = !outTo2; // change state for next time
}
} catch(InterruptedException e) {
print("Alternator interrupted");
}
print("Alternator off");
}
} // Accepts toasts on either channel, and relays them on to
// a "single" successor
class Merger implements Runnable {
private ToastQueue in1Queue, in2Queue, toBeButteredQueue,
toBeJammedQueue, finishedQueue;
public Merger(ToastQueue in1, ToastQueue in2,
ToastQueue toBeButtered, ToastQueue toBeJammed,
ToastQueue finished) {
in1Queue = in1;
in2Queue = in2;
toBeButteredQueue = toBeButtered;
toBeJammedQueue = toBeJammed;
finishedQueue = finished;
}
public void run() {
try {
while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
Toast t = null;
while(t == null) {
t = in1Queue.poll(50, TimeUnit.MILLISECONDS);
if(t != null)
break;
t = in2Queue.poll(50, TimeUnit.MILLISECONDS);
}
// Relay toast onto the proper queue
switch(t.getStatus()) {
case BUTTERED:
toBeJammedQueue.put(t);
break;
case JAMMED:
toBeButteredQueue.put(t);
break;
default:
finishedQueue.put(t);
}
}
} catch(InterruptedException e) {
print("Merger interrupted");
}
print("Merger off");
}
} public class E29_ToastOMatic2 {
public static void main(String[] args) throws Exception {
ToastQueue
dryQueue = new ToastQueue(),
butteredQueue = new ToastQueue(),
toBeButteredQueue = new ToastQueue(),
jammedQueue = new ToastQueue(),
toBeJammedQueue = new ToastQueue(),
finishedQueue = new ToastQueue();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Toaster(dryQueue));
exec.execute(new Alternator(dryQueue, toBeButteredQueue,
toBeJammedQueue));
exec.execute(
new Butterer(toBeButteredQueue, butteredQueue));
exec.execute(
new Jammer(toBeJammedQueue, jammedQueue));
exec.execute(new Merger(butteredQueue , jammedQueue,
toBeButteredQueue, toBeJammedQueue, finishedQueue));
exec.execute(new Eater(finishedQueue));
TimeUnit.SECONDS.sleep(5);
exec.shutdownNow();
}
} /* (Execute to see output) *///:~

没想清楚前不许偷看!

因为代码比较长,推荐把代码导入IDE查看。

建房子之前先挖地基 - Java BlockingQueue理解的更多相关文章

  1. 【vijos】1750 建房子(线段树套线段树+前缀和)

    https://vijos.org/p/1750 是不是我想复杂了.... 自己yy了个二维线段树,然后愉快的敲打. 但是wa了两法.......sad 原因是在处理第二维的更新出现了个小问题,sad ...

  2. 用eclipse 检索SVN 上 myEclipse 建的web项后,成java项目解决方法

    用eclipse 检索SVN 上 myEclipse 建的web项后,成java项目解决方法 在网上找了非常多,都无论用. 说添加.project 文件几个属性.但我发现里面都有,在我这里无论什么用. ...

  3. Java BlockingQueue Example(如何使用阻塞队列实现生产者-消费者问题)

    Today we will look into Java BlockingQueue. java.util.concurrent.BlockingQueue is a java Queue that ...

  4. 深挖的Java源代码之Integer.parseInt()vs Integer.valueOf()

    Integer.parseInt()和Integer.valueOf()都是用来将String转换为Int的,但是为什么Java会提供两个这样的方法呢,他们如果是同样的操作,岂不是多此一举? 我们来深 ...

  5. Java BlockingQueue是什么?

    Java BlockingQueue是一个并发集合util包的一部分.BlockingQueue队列是一种支持操作,它等待元素变得可用时来检索,同样等待空间可用时来存储元素.

  6. JAVA个人理解

    为了找到别人写的好文章,先分享下自己的知识,找找感觉路线. 学java前接触的c,后来转向java.第一个照面理解的就是面向对象,没想到让我想了好多年.当时有个负责任的老师说面向对象这个词具体释义众说 ...

  7. [java] 深入理解内部类: inner-classes

    [java] 深入理解内部类: inner-classes // */ // ]]>   [java] 深入理解内部类: inner-classes Table of Contents 1 简介 ...

  8. Java初始化理解与总结 转载

    Java的初始化可以分为两个部分: (a)类的初始化 (b)对象的创建 一.类的初始化 1.1 概念介绍: 一个类(class)要被使用必须经过装载,连接,初始化这样的过程. 在装载阶段,类装载器会把 ...

  9. 从Java视角理解CPU上下文切换(Context Switch)

    从Java视角理解系统结构连载, 关注我的微博(链接)了解最新动态   在高性能编程时,经常接触到多线程. 起初我们的理解是, 多个线程并行地执行总比单个线程要快, 就像多个人一起干活总比一个人干要快 ...

随机推荐

  1. iOS设计模式——单例模式

    单例模式用于当一个类只能有一个实例的时候, 通常情况下这个“单例”代表的是某一个物理设备比如打印机,或是某种不可以有多个实例同时存在的虚拟资源或是系统属性比如一个程序的某个引擎或是数据.用单例模式加以 ...

  2. iOS面试题05-父子控制器、内存管理

    内存管理.父子控制器面试题 1.建立父子关系控制器有什么用 回答:1>监听屏幕选中 2>如果想拿到你当前的很小的一个控制器所在的导航控制器必须要跟外面比较大的控制器建立父子关系,才能一层一 ...

  3. iOS 用UISearchDisplayController实现查找功能

    UISearchDisplayController是iOS中用于处理搜索功能的控制器,此控制器需要和UISearchBar结合使用 示例代码如下: // // WKRootViewController ...

  4. [LeetCode]题解(python):126-Word Ladder II

    题目来源: https://leetcode.com/problems/word-ladder-ii/ 题意分析: 给定一个beginWord和一个endWord,以及一个字典单词,找出所有从begi ...

  5. app 转caf 音频 代码

    afconvert /Users/xiaoye/Downloads/cat.caf     /Users/xiaoye/Downloads/cat1.caf  -d ima4 -f caff -v;

  6. The process "E:\Qt\4.8.5\bin\qmake.exe" exited with code 2.(不能包含中文路径,qmake够弱智的)

    打开某个项目的时候,编译出现类似的错误 21:46:44: The process "E:\Qt\4.8.5\bin\qmake.exe" exited with code 2. ...

  7. Qt开发小工具之gif转换器(使用QMovie截取每一帧为QImage,然后用QFile另存为图片文件)

    最近,QQ上好多各种gif表情.每一个都很经典呀..于是我就想把它转换成一张张静态图片...没学过ps.于是写了几行代码.完工.核心代码如下 主要是借助QMovie类.文件读取模式选择QMovie:: ...

  8. android学习----overridePendingTransition

    1 Activity的切换动画指的是从一个activity跳转到另外一个activity时的动画. 它包括两个部分:一部分是第一个activity退出时的动画:另外一部分时第二个activity进入时 ...

  9. Android Studio ADB响应失败解决方法

    当启动Android Studio时,如果弹出 adb not responding. you can wait more,or kill "adb.exe" process ma ...

  10. HDU3966(树链剖分)

    题目:Aragorn's Story 题意:给一棵树,并给定各个点权的值,然后有3种操作: I C1 C2 K: 把C1与C2的路径上的所有点权值加上K D C1 C2 K:把C1与C2的路径上的所有 ...