Producer-Consumer Pattern
来源:互联网 发布:macbook怎样下载软件 编辑:程序博客网 时间:2024/06/05 02:00
什么是Producer-Consumer Pattern?
设想一个场景,生产者需要将数据安全地交给消费者,然而生产者和消费者运行在不同的线程上,两者的处理速度差将是最大的问题。消费者想要取出数据的时候生产者还没有建立数据,或者生产者建立数据的时候消费者还没办法接受数据等等。
这个模型就是在他们中间加入一个桥梁,处理线程之间的处理速度差。
当两方都只有一个的时候,我们也称之为Pipe Pattern(管道模型)
同样的,我们还是用代码来做一个示例,设想现在有一个场景,有一个桌子,桌子上最多能放3块蛋糕,3个厨师往桌子上制作蛋糕,3个食客吃桌子上的蛋糕,当桌子上的蛋糕个数等于3个的时候,厨师就不能再放蛋糕了,当桌子上的蛋糕等于0个的时候,食客就不能吃蛋糕了。我们来实现一下:
首先写一个table类:
package producerConsumerPattern;public class Table { /* * 原理上是循环队列 * */ private final String[] buffer;//存放蛋糕 private int tail;//尾巴 private int head;//头部 private int count;//buffer的蛋糕个数 public Table(int count){ this.buffer = new String[count];//最大能放的蛋糕个数 this.head = 0; this.tail = 0; this.count = 0; //原理上是循环队列 } //放置蛋糕 抛出的异常表示这个方法可被打断 public synchronized void put(String cake) throws InterruptedException{ while(count>=buffer.length){//警戒条件 当count<buffer.length的时候不能再放置 wait(); } //进入临界区 buffer[tail] = cake;//放置在尾部 tail = (tail+1)%buffer.length; count++; System.out.println(Thread.currentThread().getName()+"puts "+cake+" ,还剩"+count+"块蛋糕"); notifyAll();//警戒条件可能发生了改变 对其他所有线程进行唤醒 } //获取蛋糕 public synchronized String take() throws InterruptedException{ while(count<=0){//警戒条件 当count>0的时候才可以拿 wait(); } String cake = buffer[head];//拿出头 head = (head + 1)%buffer.length; count--; System.out.println(Thread.currentThread().getName()+" takes "+cake+" ,还剩"+count+"块蛋糕"); notifyAll();//警戒条件可能发生了改变 对其他所有线程进行唤醒 return cake; }}然后写食客线程:
package producerConsumerPattern;import java.util.Random;public class EaterThread implements Runnable { private final Table table; private final Random random; public EaterThread(Table table , long seed){ this.table = table; this.random = new Random(seed); } @Override public void run() { try{ while(true){ table.take(); Thread.sleep(random.nextInt(1000));//模拟吃蛋糕的间隔 } } catch(InterruptedException e){ } } }实现厨师线程类:
package producerConsumerPattern;import java.util.Random;public class MakerThread implements Runnable { /* * 生产蛋糕的线程 * */ private final Random random;//随机产生生产蛋糕的时间 private final Table table;//放置的桌子 private static int id = 0;//蛋糕编号 public MakerThread(Table table,long seed){ this.table = table; this.random = new Random(seed); } @Override public void run() { try{ while(true){ String cake = "No. "+nextId(); table.put(cake); Thread.sleep(random.nextInt(1000));//随机休眠 } } catch(InterruptedException e){ e.printStackTrace(); } } private static synchronized int nextId(){ return id++; }}最后写测试类来进行测试:
package producerConsumerPattern;public class Test { public static void main(String[] args) { Table table = new Table(10);//可以放置3块蛋糕 EaterThread eaterThread = new EaterThread(table,System.currentTimeMillis()); MakerThread makerThread = new MakerThread(table,System.currentTimeMillis()); new Thread(eaterThread,"食客1").start(); new Thread(eaterThread,"食客2").start(); new Thread(eaterThread,"食客3").start(); new Thread(makerThread,"厨师1").start(); new Thread(makerThread,"厨师2").start(); new Thread(makerThread,"厨师3").start(); }}
运行结果如下:
现在我们回头看一下代码,实际上和我们之前说的guarded suspention pattern很类似,在Table这个类中,对于put方法和take方法都存在他自己的警戒条件,满足警戒条件就进入临界区,否则就进入wait set。我们在这两个方法都抛出了InterruptedException,说明这个方法是可以取消的方法。
关键在于保护安全性的table类,这个类控制了生产者消费者的共享互斥,关于synchronized、wait、notifyAll这些考虑多线程操作的代码,全都隐藏在Table类里,这也是这个模式的关键。
Producer-Consumer Pattern的所有参与者
1、data参与者
data参与者由producer参与者建立,由consumer参与者所使用
2、producer参与者
producer参与者建立data参与者,传递给channel参与者
3、consumer参与者
consumer参与者从channel参与者获取data参与者
4、channel参与者
channel参与者从producer参与者接收data参与者,并且保管起来。根据consumer的要求,将data参与者传送出去,为了确保安全,producer和consumer应该和访问进行共享排斥。(将访问方法放在channel对象里,用synchronized关键字进行修饰可以有效排斥)这个类对于生产者消费者的访问关系调控起到了极其重要的作用。
如果没有这个channel参与者,消费者处理的时间也在生产者生产的时间内,这很不合理(设想一下厨师做完直接送到客户嘴边,等客户吃完然后再做另一个蛋糕,这不合理)。
InterruptedException异常
在示例代码中,我们提到了InterruptedException,这通常表示着:
1、这是一个需要花点时间的方法
2、这是一个可以取消的方法
三个常用的抛出InterruptedException的方法有
1、java.lang.Object类的wait方法
花费时间:进入等待区需要被notify/notifyAll,在等待的期间,线程不会活动,因此需要花费时间。
取消操作:使用notify/notifyAll方法取消
2、java.lang.Thread类的sleep方法
花费时间:会暂停执行参数内设置的时间
取消操作:等待设置长度时间
3、java.lang.Thread类的join方法
花费时间:会等待到指定的线程结束为止,也会花费直到指定线程结束之前这段时间。
取消操作:等待指定线程结束
取消线程sleep()暂停状态的interrupt方法
现在存在一个线程a,a调用了sleep方法:Thread.sleep(9999999999);进行休眠着,对于另一个线程b,可以执行下面的语句a.interrupt();使得a放弃等待操作。
在执行interrupt方法的时候,不需要获取Thread实例的锁,任何线程在任何时刻都可以调用其他线程的interrupt方法。当sleep中的线程被调用interrupt方法会抛出InterruptedException异常,比如上方a线程抛出异常,这样a的控制权,就交给捕捉这个异常的catch块了。
取消线程wait()等待状态的interrupt方法
当线程a在wait()等待的时候,也可以调用interrupt方法进行取消,表示不用等notify/notifyAll了,从等待区里直接出来,同样,a线程抛出InterruptedException异常。
但是当线程wait的时候,小心锁的问题,线程进入等待区的时候,会解除锁,当wait中的线程被调用interrupt的时候,会重新获取锁定,再抛出InterruptedException。获取锁之前,无法抛出这个异常。
notify方法与Interrupt方法
notify/notifyAll是Object类的方法,是该实例的等待区调用的,而不是对线程直接调用,notify/notifyAll方法所唤醒的线程会进入wait下一个语句。执行该方法需要获取锁。
interrupt方法是Thread的方法,对线程直接调用,当被interrupt的线程正在sleep或者wait的时候,会抛出InterruptedException异常。执行该方法不需要获取锁。
join方法和interrupt方法
当线程以join等待其他线程结束的时候,可以interrupt方法取消,和sleep时一样,会跳到catch模块。
Interrupt方法只是改变了中断状态
实际上这个方法只是改变了线程的中断状态(表示这个线程有没有被中断的状态)。不是一被调用interrupt方法就抛出InterruptedException。之所以有时候会抛出这个异常,是因为sleep、wait、join这些方法会不断检查中断状态的值,然后抛出InterruptedException,如果没有执行到这些方法,就不会抛出异常,而是一直进行自己的操作,直到执行到这些方法,才马上抛出InterruptedException。
总之,没有调用sleep、wait、join,那么InterruptedException时不会抛出的。
isInterrupted方法----检查中断状态
线程中断,返回true,没有中断,返回false。
Thread.interruptted方法----检查并且清除中断状态
检查当前线程是否中断,中断返回true并且设置为非中断状态,否则返回false。
interrupted方法和interrupt方法的区别:
interrupt方法将线程切换到中断状态
interrupted方法检查并且清除中断状态
不要使用Thread类的stop方法,很危险,因为就算线程在执行临界区间的内容,也会结束线程!
- Producer-Consumer Pattern
- Producer-Consumer Pattern
- Producer-Consumer Pattern
- Java线程之Producer-Consumer Pattern
- producer consumer
- producer & consumer
- producer-consumer Pattern ---java多线程编程模式(五)
- 多线程设计模式之——Producer-Consumer Pattern
- RT-Thread: Design pattern of the Producer-consumer model (Semaphore)
- The Producer-Consumer Problem
- compile producer-consumer
- ProtoThreads - Producer&Consumer
- ACE_Task Consumer-Producer
- Producer-consumer problem
- Producer Consumer 模式
- Semaphore Consumer-Producer Code
- BufferQueue/consumer/producer
- Producer-Consumer 生产者,消费者
- Maven选择JDK8开发和编译
- 浅谈ajax
- 个人总结48
- Boost电路的电感计算
- MyEclipse使用总结——MyEclipse10安装SVN插件
- Producer-Consumer Pattern
- 浏览器与服务器的通信
- 防火墙配置基本
- activeMQ实践(二)--简单应用实例
- 数据结构实验之图论六:村村通公路
- javascript的数据类型
- Tmux使用教程
- django-5-高级
- 一个很有趣的javascript题目