多线程程序设计学习(6)Producer-Consumer模式
Producer-Consumer【生产消费者模式】
一:Producer-Consumer pattern的参与者
--->产品(蛋糕)
--->通道(传递蛋糕的桌子)
--->生产者线程(制造蛋糕的线程)
--->消费者线程(吃掉蛋糕的线程)
二:Producer-Consumer pattern模式什么时候使用
--->大量生产+大量消费的模式
三:Producer-Consumer pattern思考
--->【生产消费者模式】,肩负保护数据安全性使命的是通道参与者。通道参与者参与进行线程间的共享互斥,让生产着能正确将数据传递到消费者手中。
--->通道(桌子)的put和take方法都使用了【独木桥模式】,而生产者和消费者都不想依赖table类的详细实现,也就说,生产者不必理会其他线程,只管生产并put,同样消费者也不必理会其他线程,只管take就好。而线程的共享互斥,synchronized,wati,notifyAll这些考虑的多线程操作的代码,全都隐藏在通道table类里。提高了table的复用性。
--->生产者消费者模式,存在两方处理速率不同的话,必然造成一方等待,或占用通道大量内存的问题。
--->多线程合作的口决
线程的合作要想:放在中间的东西
线程的互斥要想:应该保护的东西
--->多生产者对单一消费者,如果情况合理,一方可以不用考虑互斥,就不用加锁,提升性能。
四进阶说明
--->习惯编写java多线程。当所调用的方法抛出,或内部抓住异常:InterruptedException.
==>通常传递给我们两个信息。(1)这是‘需要花点时间’的方法(2)这是‘可以取消’的方法
==>告诉我们方法内部有这三个选手:java.lang.Object类里的wait方法
java.lang.Thread类里的sleep方法
java.lang.Thread类里的join方法
--->需要花点时间的方法
wait==>执行wait方法的线程,进入wait set里休眠,等待notify,notifyAll唤醒。在休眠期间不会活动,因此需要花费时间。
sleep==>执行sleep,会暂停执行参数内所设置的时间,这也是需要花费时间
join==>会等待制定的线程结束为止。才执行本线程。也就是花费直到制定线程结束之前的这段时间
--->可以取消的方法。
因为需要花费时间,会降低程序的响应性,所以我们会希望像下面这样可以在中途放弃(取消)执行这个方法
1取消wait方法等待notify,notifyAll唤醒的操作
2取消sleep方法等待设置长度时间的操作
3取消join方法等待其他线程结束的操作
--->取消线程等待的详细解说
(1) A线程的实例为athread,线程体内部执行Thread.sleep(1000)时,想取消其睡眠状态。则需要B线程中取消。
--->在B线程中用athread.interrupt().
--->则A线程终止睡眠,并抛出或被抓住InterruptedException
--->这个时候A线程的catch块的代码,至关重要。
(2) A线程的实例为athread,线程体内部执行wait()时,想取消等待notify,notifyAll唤醒的操作。则需要B线程中取消。
--->在B线程体中用athread.interrupt().
--->则A线程终止等待状态,并尝试重新获取锁定。
--->获取锁定后,抛出或被抓住InterruptedException
--->这个时候A线程的catch块的代码,至关重要。
(3)A线程的实例为athread,线程体内部执行join(),想等待其他线程执行结束。则需要B线程中取消。
--->在B线程中用athread.interrupt().
--->则A线程终止睡眠,并抛出或被抓住InterruptedException
--->这个时候A线程的catch块的代码,至关重要。
--->线程对象.interrupt(),Thead.interrupted,线程对象.isinterrupted()区别
==>interrupt()让等待或休眠的线程变成中断状态,抛出异常
==>interrupted()检查线程的中断状态
是中断状态,返回true,并将中断状态修改成非中断状态
不是中断状态,返回false,不做任何操作
==>isinterrupted简单的检查线程是否为中断状态,是返回true,不是返回false,不做任何操作
Producer-Consumer案例
三个生产蛋糕的线程,三个消费蛋糕的线程,一个传递蛋糕的桌子。
传递蛋糕的桌子
1 package com.yeepay.sxf.thread5; 2 /** 3 * 在消费线程和生产线程中间起传递作用的桌子 4 * @author sxf 5 * 6 */ 7 8 public class Table { 9 //存放蛋糕的数组 10 private String[] cakes; 11 //下一个放蛋糕的位置 12 private int nextPut; 13 //下一个取蛋糕的位置 14 private int nextGet; 15 //蛋糕数组中蛋糕的数量 16 private int count; 17 //构造器 18 public Table (int count){ 19 this.cakes=new String[count]; 20 this.nextGet=0; 21 this.nextPut=0; 22 } 23 //存放蛋糕 24 public synchronized void putCakes(String cake) throws InterruptedException{ 25 System.out.println("["+Thread.currentThread().getName()+"]put"+cake); 26 //警戒条件 如果桌子上蛋糕,慢了,就阻塞生产线程。 27 while (count>=cakes.length) { 28 wait(); 29 } 30 //将蛋糕放入模拟队列 31 cakes[nextPut]=cake; 32 //算出下一个放蛋糕的位置 33 nextPut=(nextPut+1)%cakes.length; 34 //蛋糕数据量加1 35 count++; 36 //唤醒别的线程 37 notifyAll(); 38 } 39 40 //取蛋糕 41 public synchronized String takeCake() throws InterruptedException{ 42 //判断桌子上是否有蛋糕,如果没有,阻塞线程 43 while (count<=0) { 44 wait(); 45 } 46 //取出蛋糕 47 String cake=cakes[nextGet]; 48 //计算出下一个取蛋糕的位置 49 nextGet=(nextGet+1)%cakes.length; 50 //蛋糕数量减一 51 count--; 52 //唤醒其他线程 53 notifyAll(); 54 System.out.println("【"+Thread.currentThread().getName()+"】get"+cake); 55 return cake; 56 } 57 }
生产蛋糕的线程
1 package com.yeepay.sxf.thread5; 2 /** 3 * 制造蛋糕线程 4 * @author sxf 5 * 6 */ 7 public class MakeCakeThread implements Runnable{ 8 //存放蛋糕的桌子 9 private Table table; 10 11 //构造器 12 public MakeCakeThread(Table table) { 13 this.table=table; 14 } 15 16 @Override 17 public void run() { 18 while (true) { 19 for (int i = 0; i <100; i++) { 20 21 try { 22 //生产蛋糕,并放入 23 table.putCakes(Thread.currentThread().getName()+"的蛋糕"+i); 24 //当前线程休息1秒钟 25 26 Thread.sleep(1000); 27 } catch (InterruptedException e) { 28 // TODO Auto-generated catch block 29 e.printStackTrace(); 30 } 31 } 32 33 } 34 35 } 36 37 38 }
吃掉蛋糕的线程
1 package com.yeepay.sxf.thread5; 2 /** 3 * 吃蛋糕的线程 4 * @author sxf 5 * 6 */ 7 public class EatCakeThread implements Runnable { 8 //桌子 9 private Table table; 10 //构造器 11 public EatCakeThread(Table table){ 12 this.table=table; 13 } 14 /** 15 * 线程体 16 */ 17 @Override 18 public void run() { 19 while(true){ 20 try { 21 String cake=table.takeCake(); 22 Thread.sleep(500); 23 } catch (InterruptedException e) { 24 // TODO Auto-generated catch block 25 e.printStackTrace(); 26 } 27 } 28 29 } 30 31 }
测试类
1 package com.yeepay.sxf.thread5; 2 /** 3 * 测试类 4 * @author sxf 5 * 6 */ 7 public class Test { 8 9 public static void main(String[] args) { 10 //声明一张桌子 11 Table table=new Table(4); 12 //声明生产蛋糕的线程 13 Thread makeThread1=new Thread(new MakeCakeThread(table)); 14 makeThread1.setName("尚晓飞师傅"); 15 Thread makeThread2=new Thread(new MakeCakeThread(table)); 16 makeThread2.setName("范林军师傅"); 17 Thread makeThread3=new Thread(new MakeCakeThread(table)); 18 makeThread3.setName("黄栓林师傅"); 19 20 //声明吃蛋糕的线程 21 Thread eatThread1=new Thread(new EatCakeThread(table)); 22 eatThread1.setName("顾客1"); 23 Thread eatThread2=new Thread(new EatCakeThread(table)); 24 eatThread2.setName("顾客2"); 25 Thread eatThread3=new Thread(new EatCakeThread(table)); 26 eatThread3.setName("顾客3"); 27 28 //启动线程 29 makeThread1.start(); 30 makeThread2.start(); 31 makeThread3.start(); 32 eatThread1.start(); 33 eatThread2.start(); 34 eatThread3.start(); 35 36 37 } 38 }
测试结果:
[尚晓飞师傅]put尚晓飞师傅的蛋糕0
[范林军师傅]put范林军师傅的蛋糕0
【顾客1】get尚晓飞师傅的蛋糕0
【顾客2】get范林军师傅的蛋糕0
[黄栓林师傅]put黄栓林师傅的蛋糕0
【顾客3】get黄栓林师傅的蛋糕0
[尚晓飞师傅]put尚晓飞师傅的蛋糕1
【顾客3】get尚晓飞师傅的蛋糕1
[范林军师傅]put范林军师傅的蛋糕1
【顾客1】get范林军师傅的蛋糕1
[黄栓林师傅]put黄栓林师傅的蛋糕1
【顾客2】get黄栓林师傅的蛋糕1
[尚晓飞师傅]put尚晓飞师傅的蛋糕2