生产者消费者模型、仓储模型--学习JavaEE的day31下

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;
	}

生产者消费者模型和仓储模型的区别

生产者消费者模型:重点加锁的逻辑放在线程中(注重线程如何写)

仓储模型:重点加锁的逻辑放在线程外(注重仓库如何写)

总结:

6.生产者消费者模型 – wait()/notify()/notifyAll()

7.仓储模型 – wait()/notify()/notifyAll()

  • 24
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值