day31下
生产者消费者模型
最终目的:生产一个消费一个
分析:
产品类 – Phone(brand、price)
生产者线程 – Produce
消费者线程 – Consumer
生产者消费者理解图
场景1:一个生产者一个消费者的情况
步骤:
1.生产者线程、消费者线程不断的操作同一个产品对象
脏数据:
null – 0.0(Phone phone = new Phone();Consumer抢到资源输出)
华为 – 0.0(Produce才phone.setBrand(“华为”);Consumer抢到资源输出)
public class Phone {
private String brand;
private double price;
private boolean store;
//有参、无参构造、get、set、toString方法
}
//生产者线程
public class Produce extends Thread{
private Phone phone;
public Produce(Phone phone) {
this.phone = phone;
}
@Override
public void run() {
while(true){
phone.setBrand("华为");
phone.setPrice(3999);
}
}
}
//消费者线程
public class Consumer extends Thread{
private Phone phone;
public Produce(Phone phone) {
this.phone = phone;
}
@Override
public void run() {
while(true){
System.out.println(phone.getBrand() + " -- " + pho ne.getPrice());
}
}
}
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
Produce p = new Produce(phone);
Consumer c = new Consumer(phone);
p.start();
c.start();
}
}
2.两个产品之间来回切换(目的:放大步骤1的问题)
脏数据:
null – 0.0
华为 – 0.0
小米 – 3999.0
华为 – 1999.0
出现原因:设置完品牌后,还没来得及设置价格,就被消费者线程抢到资源
解决方案:生产者线程设置完品牌+价格后,消费者线程才能执行自己的代码 – 加锁
加锁:生产者线程和消费者线程都加锁才行,锁对象同一个才能互斥住,所以使用phone
但依然会有脏数据:null – 0.0
//生产者线程
public class Produce extends Thread{
private Phone phone;
public Produce(Phone phone) {
this.phone = phone;
}
@Override
public void run() {
boolean flag = true;
while(flag){
if(flag){
phone.setBrand("华为");
phone.setPrice(3999);
}else{
phone.setBrand("小米");
phone.setPrice(1999);
}
flag = !flag;
}
}
}
解决方案
//生产者线程
while(flag){
synchronized(phone){
if(flag){
phone.setBrand("华为");
phone.setPrice(3999);
}else{
phone.setBrand("小米");
phone.setPrice(1999);
}
flag = !flag;
}
}
//消费者线程
while(true){
synchronized(phone){
System.out.println(phone.getBrand() + " -- " + pho ne.getPrice());
}
}
3.生产一个消费一个
产品类加一个库存的属性 – boolean store = false
生产者线程在每次生产之前都得判断是否有库存,有就等待(等待消费者线程消费后再生产),没有就生产
消费者线程在每次消费之前都得判断是否有库存,有就消费,没有就等待(等待生产者线程生产后再消费)
生产者线程和消费者线程互相唤醒
思路:
在生产者线程run()里面先if(phone.isStore()){等待}后生产最后phone.setStore(true);唤醒
在消费者线程run()里面先if(!phone.isStore()){等待}后消费最后phone.setStore(false);唤醒
直接改:思路没问题,但代码会出现报错(在整个项目中还有其他线程,不能确保是这两个线程相互等待、唤醒)
解决方法:给等待和唤醒都添上同一对象监视器phone.wait();phone.notify();
补充:这里phone有多种角色:锁对象、产品对象、对象监视器
public class Phone {
private String brand;
private double price;
private boolean store;
//产品类加一个库存的属性,也添加get、set方法
private boolean store;
//有参、无参构造、get、set、toString方法
}
//生产者线程
public class Produce extends Thread{
private Phone phone;
public Produce(Phone phone) {
this.phone = phone;
}
@Override
public void run() {
boolean flag = true;
while(true){
synchronized(phone){
if(phone.isStore()){
try {
/**
* 等待:
* 1.释放锁资源
* 2.在对象监视器(phone)中记录当前线程被等待(阻塞)
* 3.当前线程进入到阻塞状态
*/
phone.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(flag){
phone.setBrand("华为");
phone.setPrice(3999);
}else{
phone.setBrand("小米");
phone.setPrice(1999);
}
flag = !flag;
phone.setStore(true);
phone.notify();//唤醒:唤醒对象监视器中任意一个线程
}
}
}
}
//消费者线程
public class Consumer extends Thread{
private Phone phone;
public Produce(Phone phone) {
this.phone = phone;
}
@Override
public void run() {
while(true){
synchronized(phone){
if(!phone.isStore()){
try {
/**
* 等待:
* 1.释放锁资源
* 2.在对象监视器(phone)中记录当前线程被等待(阻塞)
* 3.当前线程进入到阻塞状态
*/
phone.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(phone.getBrand() + " -- " + phone.getPrice());
phone.setStore(false);
phone.notify();//唤醒:唤醒对象监视器中任意一个线程
}
}
}
}
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
Produce p = new Produce(phone);
Consumer c = new Consumer(phone);
p.start();
c.start();
}
}
场景2:多个生产者多个消费者的情况
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
Produce p1 = new Produce(phone);
Produce p2 = new Produce(phone);
Consumer c1 = new Consumer(phone);
Consumer c2 = new Consumer(phone);
p1.start();
p2.start();
c1.start();
c2.start();
}
}
添加多个生产者多个消费者
出现问题:出现连续重复的产品,甚至直接卡死
卡死出现原因:【phone.notify();//唤醒:唤醒对象监视器中任意一个线程】
开始store为false,c1抢到资源,对象监视器:c1被阻塞;c2抢到资源,c2也被阻塞;
p1抢到资源生产,store变为true,会唤醒一个线程;
比如唤醒c1,【现在就p1、p2、c1存活】现在store为true有库存,p1就会被阻塞,p2又抢到资源,p2被阻塞;
现在就c2存活,消费,store变为false,会唤醒一个线程;
恰好唤醒c2,【现在c1、c2存活】store为false没有库存,c2被阻塞;c1抢到资源,c1也被阻塞;
就卡死了。
解决卡死方案:判断库存不用原来的if,改用while;唤醒不用原来的任意一个,改用phone.notifyAll();//唤醒:唤醒对象监视器中所有线程
出现连续重复的产品原因:线程调用run(),会出现多个变量flag
解决出现连续重复的产品方案:将变量flag放在run()外面并设置为静态,确保为同一个变量(放在run()里面也行,这里为了区分前面代码)
补充:if判断库存时,会直接往下执行,唤醒程序后while会重新判断
//生产者线程
public class Produce extends Thread{
private Phone phone;
public Produce(Phone phone) {
this.phone = phone;
}
private static boolean flag = true;
@Override
public void run() {
while(true){
synchronized(phone){
while(phone.isStore()){
try {
/**
* 等待:
* 1.释放锁资源
* 2.在对象监视器(phone)中记录当前线程被等待(阻塞)
* 3.当前线程进入到阻塞状态
*/
phone.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(flag){
phone.setBrand("华为");
phone.setPrice(3999);
}else{
phone.setBrand("小米");
phone.setPrice(1999);
}
flag = !flag;
phone.setStore(true);
phone.notifyAll();//唤醒:唤醒对象监视器中所有线程
}
}
}
}
//消费者线程同理更改
仓储模型
生产者线程不断的生产蛋糕(将蛋糕对象添加到仓库)
消费者线程不断的消费蛋糕(将蛋糕对象从仓库中取出)
先生产的蛋糕,先卖出
分析:
1.蛋糕类、仓库类、生产者线程、消费者线程
2.仓库类里有一个存放蛋糕对象的集合 – LinkedList
注意:LinkedList的队列模式
场景1:一个生产者线程一个消费者线程的情况
//蛋糕类
public class Cake {
private String brand;
private String datetime;
//有参、无参构造、get、set、toString方法
}
//仓库类
public class Store {
private int curCapacity;//当前容量
private int maxCapacity;//最大容量
private LinkedList<Cake> list;//蛋糕容器
public Store(int maxCapacity) {
this.maxCapacity = maxCapacity;
list = new LinkedList<>();//初始化
}
//入库(此方法被生产者线程不断的调用)
public synchronized void push(Cake cake){
list.add(cake);
curCapacity++;
System.out.println("入库 - 当前容量为:" + curCapacity);
}
//出库(此方法被消费者线程不断的调用)
public synchronized Cake pop(){
Cake cake = list.removeFirst();
curCapacity--;
System.out.println("出库 - 当前容量为:" + curCapacity + ",卖出的蛋糕为:" + cake);
return cake;
}
}
//生产者线程
public class Produce extends Thread{
private Store store;//传进来
public Produce(Store store) {
this.store = store;
}
@Override
public void run() {
while(true){
Cake cake = new Cake("桃李", LocalDateTime.now().toString());
store.push(cake);
}
}
}
//消费者线程
public class Consumer extends Thread{
private Store store;
public Consumer(Store store) {
this.store = store;
}
@Override
public void run() {
while(true){
store.pop();
}
}
}
public class Test01 {
public static void main(String[] args) {
Store store = new Store(20);
Produce p = new Produce(store);
Consumer c = new Consumer(store);
p.start();
c.start();
}
}
问题1:开始报错,只有入库
出现原因:消费者线程抢到资源就会报错,仓库没有可出,Cake cake = list.removeFirst();就会报错【把生产者线程停掉更可见】。生产者线程不断入库
注意:多线程使用同一个资源必须加锁
解决方案:对仓库的同步方法加锁synchronized,锁对象是谁,监视器对象就可以用谁
//入库(此方法被生产者线程不断的调用)
public synchronized void push(Cake cake){
if(curCapacity >= maxCapacity){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(cake);
curCapacity++;
System.out.println("入库 - 当前容量为:" + curCapacity);
this.notify();
}
//出库(此方法被消费者线程不断的调用)
public synchronized Cake pop(){
if(curCapacity <= 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Cake cake = list.removeFirst();
curCapacity--;
System.out.println("出库 - 当前容量为:" + curCapacity + ",卖出的蛋糕为:" + cake);
this.notify();
return cake;
}
场景2:多个生产者线程多个消费者线程的情况
public class Test01 {
public static void main(String[] args) {
Store store = new Store(20);
Produce p1 = new Produce(store);
Produce p2 = new Produce(store);
Consumer c1 = new Consumer(store);
Consumer c2 = new Consumer(store);
p1.start();
p2.start();
c1.start();
c2.start();
}
}
出现问题:又会报错,超出库存最大容量
出现原因:
零界点:curCapacity为0
卖完curCapacity为0,c1抢到资源,被阻塞,c2抢到资源,被阻塞;
p1抢到资源,curCapacity为0,就会入库,curCapacity变为1,唤醒对象监视器中任意一个线程,比如c2,就会出库curCapacity变为0;
之后唤醒c1,curCapacity为0,就会报错。
零界点:curCapacity为20
卖完curCapacity为20,p1抢到资源,被阻塞,p2抢到资源,被阻塞;
c1抢到资源,curCapacity为20,就会出库,curCapacity变为19,唤醒对象监视器中任意一个线程,比如p2,就会入库curCapacity变为20;
之后唤醒p1,p2继续抢到资源,curCapacity为20,p2被阻塞,p1抢到资源进入,curCapacity++,curCapacity为21;
继而p1、p2相互争抢唤醒阻塞,就会不断入库
解决方案:判断仓储不用原来的if,改用while;唤醒不用原来的任意一个,改用phone.notifyAll();//唤醒:唤醒对象监视器中所有线程
补充:if判断库存时,会直接往下执行,唤醒程序后while会重新判断
//入库(此方法被生产者线程不断的调用)
public synchronized void push(Cake cake){
while(curCapacity >= maxCapacity){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(cake);
curCapacity++;
System.out.println("入库 - 当前容量为:" + curCapacity);
this.notifyAll();
}
//出库(此方法被消费者线程不断的调用)
public synchronized Cake pop(){
while(curCapacity <= 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Cake cake = list.removeFirst();
curCapacity--;
System.out.println("出库 - 当前容量为:" + curCapacity + ",卖出的蛋糕为:" + cake);
this.notifyAll();
return cake;
}
生产者消费者模型和仓储模型的区别
生产者消费者模型:重点加锁的逻辑放在线程中(注重线程如何写)
仓储模型:重点加锁的逻辑放在线程外(注重仓库如何写)