Java编程思想-并发(3)
来源:互联网 发布:淘宝助理5.8图片搬家 编辑:程序博客网 时间:2024/05/18 23:25
生产者消费者队列
使用同步队列来解决任务协作的问题比使用wait和notify更加方便,因为后者需要在每次交互时都握手。而同步队列在任何时刻只允许一个任务插入或移除元素。在java.util.concurrent.BlockingQueue接口中提供了这个队列。该接口提供了大量的标准实现:LinkedBlockingQueue、ArrayBlockingQueue等。
如果消费者任务试图从队列中获取对象,而该队列此时为空,那么这些队列还可以挂起消费者任务。所以,阻塞队列比传统的wait和notify简单、可靠。
下面的示例将多了LiftOff对象的执行串行化了,消费者是LiftOffRunner,它将每个LiftOff对象从BlockingQueue中推行并直接运行。(即,BlockingQueue将显式地调用run而使用自己的线程运行,而不是为每一个任务启动一个新的线程。)
class LiftOffRunner implements Runnable { private BlockingQueue<LiftOff> rockets; public LiftOffRunner(BlockingQueue<LiftOff> queue) { rockets = queue; } public void add(LiftOff lo) { try { rockets.put(lo); } catch(InterruptedException e) { System.out.print("Interrupted during put()"); } } //当使用BlockingQueue加入或移除任务时,BlockingQueue实际上时=是直接调用了run方法,将run在BlockingQueue开辟的线程中执行,而不是新开一个任线程调用run方法。 @Override public void run() { try { while(!Thread.interrupted()) { LoftOff rocket = rockets.take(); rocket.run(); } } catch(InterruptedException e) { System.out.print("waking from take()"); } System.out.print("Exiting LoftOffRunner"); }}public class Test { static void getKey() { try { new BufferedReader(new InputStreamReader(System.in)).readLine(); } catch(IOException e) { throw new RuntimeException(e); } } static void getKey(String message) { System.out.print(message); getKey(); } static void test(String msg, BlockingQueue<LiftOff> queue) { System.out.print(msg); LiftOffRunner runner = new LiftOffRunner(queue); Thread t = new Thread(runner); t.start(); for(int i = 0; i < 5; ++i) { runner.add(new LiftOff(5)); getKey("Press 'enter' (" + msg + ")"); t.interrupt(); System.out.print("Finished " + msg + " test"); } } public static void main(String[] args) { //无限大小的队列 test("LinkedBlockingQueue", new LinkedBlockingQueue<LiftOff>()); //固定大小的队列 test("ArrayBlockingQueue", new ArrayBlockingQueue<LiftOff>()); //size为1的队列 test("SynchronizedQueue", new SynchronizedQueue<LiftOff>()); }}
主线程中启动了一个子线程t,并传入一个LiftOffRunner任务,该子线程t将并发执行LiftOffRunner的run方法,而在run方法中显式调用了LiftOff的run方法(LiftOff也是一个Runnable),并非启动了一个新的线程,也就是说,这个程序只有两个线程,一个主线程,一个子线程,主线程负责监测从键盘中读入的enter,当敲入回车,程序退出。所有的Blocking的add操作都是在唯一的子线程中执行的,由于使用了BlockingQueue ,所有的同步可以忽略。
一个BlockingQueue的示例:吐司 BlockingQueue
下面这个BlockingQueue的示例,模拟了一台机器执行的三个任务:一个制作吐司、一个给吐司抹黄油、另一个在抹过黄油的吐司上涂果酱。我们可以使用三个BlockingQueue来模拟这个过程:
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 random = new Random(47); public Toaster(ToastQueue tq) { toastQueue = tq; } public void run() { try { while(!Thread.interrupted()) { TimeUnit.MILLSECONDS.sleep(500); Toast t = new Toast(count++); System.out.print(t); toastQueue.put(t); } } catch(InterruptedException e) { System.out.print("Toaster interrupted"); } System.out.print("Toaster off"); }}class Butterer implements Runnable { private ToastQueue dryQueue, butteredQueue; public Buttterer(ToastQueue dry, ToastQueue buttered) { dryQueue = dry; butteredQueue = buttered; } public void run() { try { while(!Thread.interrupted()) { Toast t = dryQueue.take(); t.butter(); System.out.print(t); butteredQueue.put(t); } } catch(InterruptedException e) { System.out.print("Butterer interrupted"); } System.out.print("Butterer off"); }}class Jammer implements Runnable { private ToastQueue butteredQueue, finishedQueue; public Jammer(ToastQueue buttered, ToastQueue finishedQueue) { this.butteredQueue = buttered; this.finishedQueue = finishedQueue; } public void run() { try { while(!Thread.interrupted()) { Toast t = butteredQueue.take(); System.out.print(t); finishedQueue.put(t); } } catch(InterruptedException e) { System.out.print("Jammer interrupted"); } System.out.print("Jammer off"); }}class Eater implements Runnable { public void run() { try { while(!Thread.interrupted()) { Toast t = finishedQueue.take(); if(t.getId() != Toast.Status.JAMMED) { System.out.print("Error: " + t); } else { System.out.print("Chomp! " + t); } } catch(InterrruptedException e) { System.out.print("Eater interrupted"); } } System.out.print("Eater off"); }}public class ToasterMatic { public static void main(String[] args) { ToastQueue dryQueue = new ToastQueue(); ToastQueue butteredQueue = new ToastQueue(); ToastQueue finishedQueue = new ToastQueue(); ExecutorService exec = Executors.newCachedThreadPool(); exec.exceute(new Toaster(dryQueue)); exec.exceute(new Butterer(dryQueue, butteredQueue)); exec.exceute(new Jammer(butteredQueue, finishedQueue)); exec.exceute(new Eater(finishedQueue)); TimeUnit.SECONDS.sleep(5); exec.shutdownNow(); }}
任务间使用管道进行输入/输出
通过输入/输出在线程间进行通信通常很有用。提供线程功能的类库以“管道”的形式对线程间的输入/输出类库中的对应物就是PipedWriter类(允许任务向管道写)和PipedReader类(允许不同任务从同一个管道中读取)。管道也是生产者/消费者问题的一个解决方案。管道是一个阻塞队列。在引入BlockingQueue之前,管道非常常用。
下面的例子将使用管道在两个任务间通信:
class Sender implements Runnable { private Random random = new Random(47); private PipedWriter out = new PipedWriter(); public PipedWriter getPipedWriter() { return out; } public void run() { try { while(true) { for(char c = 'A'; c <= 'z'; ++c) { out.write(c); TimeUnit.write(c); TimeUnit.MILLSECONDS.sleep(rand.nextInt(500)); } } } catch(IOException e) { System.out.print(e + " Sender write exception"); } catch(InterruptedException e) { System.out.print(e + " Sender sleep interrupted"); } }}class Receiver implements Runnable { private PipedReader in; public Receiver(Sender sender) throws IOException { in = new PipedReader(sender.getPipedWriter()); } public void run() { try { while(true) { System.out.print("Read: " + (char)in.read() + ", "); } } catch(IOException e) { System.out.print(e + "Receiver read exception"); } }}public class PipedIO { public static void main(String[] args) { Sender sender = new Sender(); Receiver receiver = new Receiver(sender); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(sender); exec.execute(receiver); TimeUnit.SECONS.sleep(4); exec.shutdown(); }}
//输出Read: A, Read: B, Read: B, Read: D, Read: E, Read: F, Read: G, Read: H, Read: I, Read: J, Read: K, Read: L, Read: M, Read: N, Read: O, java.langInterruptedException: sleep interrupted Sender sleep interruptedjava.lang.io.InterruptedIOException Receiver read exception
Sender 和Receiver表示两个需要通信的任务。Sender创建一个PipedWriter用于向“管道”中写入,而Receiver建立一个PipedReader,这个PipedReader必须知道从哪里读取,所需必须传入一个PipedWriter参数。程序将并发执行这两个任务,Sender每次向管道按照A-z写入一个英文字母,然后随机休眠0-500毫秒数,接着由Receiver读取这个英文字母并打印,程序将在4秒后中断。
从输出还可以看到,调用shutdown时,程序立马中断了,说明PipedReader是可以中断的。这也是它与普通IO的区别,后者将不能被打断(即System.in.read()将不能被打断)。
- Java编程思想-并发(3)
- Java编程思想——并发(3)
- java编程思想(读书笔记):21.并发
- Java编程思想-并发(1)
- Java编程思想-并发(2)
- Java编程思想-并发(4)
- Java编程思想-并发(5)
- Java编程思想-并发(6)
- 并发(java编程思想)笔记
- java编程思想--21 并发
- Java编程思想之并发
- Java编程思想-21并发
- JAVA编程思想笔记--并发
- (44)21.4.3 并发 练习 18---Java编程思想之并发笔记
- (45)21.4.3 并发 练习 19---Java编程思想之并发笔记
- (46)21.4.3 并发 练习 20---Java编程思想之并发笔记
- Java编程思想-java中的并发(一)
- Java编程思想-java中的并发(二)
- 插入排序、半分插入排序、希尔排序
- Android学习笔记之多线程间的异步通信
- python socket 创建tcp服务器和客户端
- 负载均衡
- 具体实例教你如何做LoadRunner结果分析
- Java编程思想-并发(3)
- Android问题—SQLite存储大二进制文件
- 快速排序
- 523. Continuous Subarray Sum
- 可靠简单的 解决Microsoft.Jet.OLEDB.4.0 不能在64位系统下使用的问题
- DER 和 PEM 格式
- B
- Altium Designer17添加3D封装后元件是绿色的
- 6.2判断一个数能否被3整除,如果不能整除就抛出一个异常,并让其调用方法来捕获,显示相应的处理信息