最近一直在看《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 toastToast 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);} elseprint("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) *///:~

View Code

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

首先可以注意到的,程序中并没有出现任何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() {returnBUTTERED.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 toastToast 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);} elseprint("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 alternationpublic 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);elseout2Queue.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 queueswitch(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查看。

转载于:https://www.cnblogs.com/andrew-chen/p/4991961.html

建房子之前先挖地基 - Java BlockingQueue理解相关推荐

  1. 从零开始学riscv之建房子,先打地基

    建房子,先打地基 文章目录 建房子,先打地基 材料准备 环境准备 第一行代码 运行起来 在开始动手写第一行代码之前,我想在此再详细介绍一下需要准备的一些材料.这些前期准备越充分,后面的工作就会相对越顺 ...

  2. 建房子 最安全图纸_妄想山海初期该怎么办?砍树狩猎建房子,还能拆别人的房子...

    妄想山海还是一个很有趣的游戏,唯一的缺点就是游戏对手机配置要求比较高,手机太差的玩家,就别想那么多了,希望等游戏正式上线,能优化的好一些吧! 在测试中,玩家能体验到游戏中的各种玩法,也是非常的有趣,而 ...

  3. 58到家:企业安全就像建房子,这几个方法得知道

    真正用服务取得用户信任的互联网平台不多,58到家就是其一:它的网站和App上所提供的服务项目,不仅关系到人们的衣食住行,有时候还要扮演"救火队员"的角色,例如开锁.维修. 每一天, ...

  4. 搜狗校招编程题-建房子

    @[TOC][搜狗校招编程题-建房子] 题目描述 某市政府规划建设一个新的小镇,要求小镇上的所有房屋都坐落在同一条东西向大姐的北侧并且临街(两座房子不能重叠).到目前为止,这条街上已经建造了n座房子, ...

  5. Java BlockingQueue

    本文翻译自http://tutorials.jenkov.com/java-util-concurrent/blockingqueue.html,机翻加人工校正,仅供学习交流. Java Blocki ...

  6. 房子并发什么意思_周公解梦:梦见建房子是什么意思

    房子是人类的容身之所,所以很多人都在为买房子而拼搏,如果能够赚到买房子的钱,那么做梦也会笑吧.但如果在梦中看见在建房子的话,是暗示着您什么呢?以下小编就给大家带来关于梦见建房子的梦境详细分析. 梦见建 ...

  7. 为什么我不同意建房子

    为什么我不同意建房子 阿奶说,家里剩点钱,房子那么值钱,我们拿自己的钱去盖个房子吧.钱放着也是贬值. 钱放着会贬值是没错,但要拿来建房子,我不同意.为什么? 第一,我们家依然是一个不富裕的家庭.买菜不 ...

  8. 当使用视觉SLAM对一个环境建图之后,如何让机器人能够“理解”地图并导航呢? - 知乎

    当使用视觉SLAM对一个环境建图之后,如何让机器人能够"理解"地图并导航呢? - 知乎

  9. 从Java代码到Java堆理解和优化您的应用程序的内存使用

    从Java代码到Java堆理解和优化您的应用程序的内存使用 简介: 本文将为您提供 Java? 代码内存使用情况的深入见解,包括将 int 值置入一个Integer 对象的内存开销.对象委托的成本和不 ...

最新文章

  1. eclipse发布web项目到生产环境的方式汇总(tomcat)
  2. 蓝桥杯练习系统习题-算法训练1
  3. python将列表的第一列删除_python列表基本操作:索引(访问),切片,扩展,删除,追加,统计,长度,获取下标等...
  4. JDK源码(7)-Boolean
  5. 关于修改docker(非docker-engine)的日志引擎
  6. vSAN其实很简单-如何榨干vSAN的最后的空间- Part2(转)
  7. error code ELIFECYCLE
  8. iOS开发之抓包工具的Charles的初步安装使用:一步一步教你学会抓包工具Charles的使用(下载破解+代理设置+证书配置)
  9. 带你深挖Java泛型类型擦除以及类型擦除带来的问题
  10. [saiku] 配置saiku实时展现查询数据
  11. Apache LoadModule php5_module 配置
  12. 利用VBB仿真——实现摇杆时钟
  13. u盘插在电脑上灯亮没有反应_如何解决U盘指示灯亮着却不显示问题
  14. 笔记本系统恢复连载之九:神舟笔记本系统恢复
  15. 计算每年的母亲节日期-C语言代码
  16. Linux后台日志定时清理脚本
  17. java咖啡是研磨的吗_没想象的困难:咖啡研磨,明白只需要三步!
  18. 5oSf6KeJ5Lul5ZCO5Y+v5Lul55SoYmFzZTY05Yqg5a+G6ZqP56yU
  19. Notion:笔记协同工具,使用教程
  20. 文化产业如何面对大数据时代

热门文章

  1. Linux学习之CentOS(三十四)--配置域主DNS服务器
  2. Office下Word直接转换成Pdf格式!
  3. 如何使用小数据集对大模型进行微调(迁移学习)-微迁移
  4. jittor和pytorch生成网络对比之cogan
  5. Android系统在新进程中启动自定义服务过程(startService)的原理分析 (下)
  6. 【Ethereum】以太坊ERC20 Token标准完整说明
  7. 大唐联仪推出下一代移动通信测试解决方案
  8. kali用Squid简单配置搭建http代理服务器
  9. [转载]javascript创建对象的几种方式
  10. visual studio 多行编辑 列编辑