37 多线程(九)——线程通信(生产者消费者问题的解决方法):管程法与信号灯法
线程通信
图片来源:尚学堂ppt
线程通信模型
管程法
案例一 没有加线程通信:
情景设置:工厂(生产者)生产馒头,仓库(缓冲器)存储馒头,商店(消费者)从仓库取走馒头,加线程安全,不加线程通信
package _20191206; /** * 生产者消费者模型:管程法 * @author TEDU * 情景设置:工厂(生产者)生产馒头,仓库(缓冲器)存储馒头,商店(消费者)从仓库取走馒头,加线程安全,不加线程通信 */ public class CoTest01 { public static void main(String[] args) { Storage storage = new Storage(); new Productor(storage).start(); new Consumer(storage).start(); } } //工厂:不止一家,多线程 class Productor extends Thread{ private Storage stor; public Productor(Storage stor) { this.stor = stor; } public void run() { for(int i = 0; i < 200;i++) { stor.push(new SteamedBun(i)); } } } //仓库:仓库就是容器,因为仓库有固定的大小,所以考虑用数组来做 class Storage{ private SteamedBun[] buns = new SteamedBun[10];//仓库的大小是固定的 private int count = 0;//仓库数组下标 //入仓 public synchronized void push(SteamedBun bun) { buns[count] = bun; System.out.println("存入第"+buns[count].getId()+"个馒头 count:"+count); count++; } //出仓 public synchronized void pop() { count--; System.out.println("取出第"+buns[count].getId()+"个馒头 count:"+count); } } //商店:不止一家(多线程) class Consumer extends Thread{ private Storage stor; public Consumer(Storage stor) { this.stor = stor; } public void run() { for(int i=0;i<200;i++) { stor.pop(); } } } //商品:馒头 class SteamedBun { private int id; public SteamedBun(int id) { this.id = id+1; } public int getId() { return id; } }
运行结果:
存入第1个馒头 count:0 存入第2个馒头 count:1 存入第3个馒头 count:2 存入第4个馒头 count:3 存入第5个馒头 count:4 存入第6个馒头 count:5 存入第7个馒头 count:6 存入第8个馒头 count:7 存入第9个馒头 count:8 存入第10个馒头 count:9 取出第10个馒头 count:9 取出第9个馒头 count:8 Exception in thread "Thread-0" 取出第8个馒头 count:7 取出第7个馒头 count:6 取出第6个馒头 count:5 取出第5个馒头 count:4 取出第4个馒头 count:3 取出第3个馒头 count:2 取出第2个馒头 count:1 取出第1个馒头 count:0 Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: -1 at _20191206.Storage.pop(CoTest01.java:42) at _20191206.Consumer.run(CoTest01.java:55) java.lang.ArrayIndexOutOfBoundsException: 10 at _20191206.Storage.push(CoTest01.java:35) at _20191206.Productor.run(CoTest01.java:24)
可以看到,取馒头时,取到仓库下标0后,再试图取已经没有可以取得馒头了,指针越界异常报出。
这个时候,我们需要引入wait与notify方法,在仓库存满时,停止生产馒头,仓库空时,停止取馒头。
案例二:加了线程通信
在案例一的基础上,我们加上wait与notifyAll方法,通过判断生产与取货的时机,来停止生产与停止取货(wait()方法)以及继续生产继续取货(notifyAll()方法)。
-
this.wait() 停止当前线程
- this.notifyAll() 激活其它线程
//入仓 public synchronized void push(SteamedBun bun) { //何时停止存?调用wait if(count == buns.length) {//如果存到下标为仓库的大小,说明满了,停止存 try { this.wait();//停止当前线程 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } buns[count] = bun; System.out.println("存入第"+buns[count].getId()+"个馒头 count:"+count); count++; //一旦开始存,就可以通知商家来取 this.notifyAll();//激活其它线程 } //出仓 public synchronized void pop() { //何时出仓? 调用wait if(count == 0) { try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } count--; System.out.println("取出第"+buns[count].getId()+"个馒头 count:"+count); //一旦开始取,仓库就不是满的,就要通知生产者生产 this.notifyAll(); }
运行结果:
信号灯法
适用:生产1个取1个的模式
情景设置:一个路口有一个斑马线信号灯,当信号灯红色,人停车走,当信号灯绿色,车停人走。
package _20191206; /** * 生产者消费者模式:信号灯法 * @author TEDU * 情景设置:一个路口有一个斑马线信号灯,当信号灯红色,人停车走,当信号灯绿色,车停人走。 * 适用:生产1个取1个的模式 */ public class CoTest02 { public static void main(String[] args) { //来一个信号灯 SignalLight light = new SignalLight("斑马线信号灯",true); new Walker(light).start(); new Car(light).start(); } } //行人 class Walker extends Thread{ private SignalLight light; public Walker(SignalLight light) { this.light = light; } public void run() { for (int i = 0; i < 10; i++) { light.forWalker(); } } } //车 class Car extends Thread{ private SignalLight light; public Car(SignalLight light) { this.light = light; } public void run() { for (int i = 0; i < 10; i++) { light.forCar(); } } } //交通灯 class SignalLight{ private boolean flag = true; //flag 值为 True 表示绿灯,行人走。值false表示红灯,车走。 private String name; public SignalLight(String name,boolean b) { this.flag = b; this.name = name; } public synchronized void forWalker() { //查看信号灯 if(!flag) {//红灯等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //绿灯走你 System.out.println(name+":绿-行人正在过马路..."); this.notifyAll(); flag = !flag; } public synchronized void forCar() { //查看信号灯 if(flag) {//绿灯等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //红灯走你 System.out.println(name+":红-车正在驶过..."); this.notifyAll(); flag = !flag; } }
结果:
斑马线信号灯:绿-行人正在过马路... 斑马线信号灯:红-车正在驶过... 斑马线信号灯:绿-行人正在过马路... 斑马线信号灯:红-车正在驶过... 斑马线信号灯:绿-行人正在过马路... 斑马线信号灯:红-车正在驶过... 斑马线信号灯:绿-行人正在过马路... 斑马线信号灯:红-车正在驶过... 斑马线信号灯:绿-行人正在过马路... 斑马线信号灯:红-车正在驶过... 斑马线信号灯:绿-行人正在过马路... 斑马线信号灯:红-车正在驶过... 斑马线信号灯:绿-行人正在过马路... 斑马线信号灯:红-车正在驶过... 斑马线信号灯:绿-行人正在过马路... 斑马线信号灯:红-车正在驶过... 斑马线信号灯:绿-行人正在过马路... 斑马线信号灯:红-车正在驶过... 斑马线信号灯:绿-行人正在过马路... 斑马线信号灯:红-车正在驶过...