多线程--多生产者多消费者问题基础

简介:

生产者消费者模式,即生产者负责生产数据,存放到队列中,消费者从队列中取出数据来消费。所以,生产者和消费者是不直接通讯的,而是通过队列进行通讯。

生产者和消费者是抽象的概念,可以是线程,进程,系统模块,而队列也可以是jvm中quene,redis中的List,甚至是数据库表,这要求我们在不同的使用场景需要选择相应的实现。


下面先通过简单的一个场景来运用这个思想。

场景:商家卖烤鸭,每生产一只烤鸭,就卖出去,卖出去之后在生产下一只,继续卖。

public class ProducerandCustomer {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
			Resouce resouce=new Resouce();
			Producer producer=new Producer(resouce);
			Customer customer=new Customer(resouce);
			
			Thread t0=new Thread(producer);
			//Thread t1=new Thread(producer);
			Thread t2=new Thread(customer);
			//Thread t3=new Thread(customer);
			
			t0.start();
			//t1.start();
			t2.start();
			//t3.start();
	}

}

class Resouce{
	private String name;
	private int count=1;
	private boolean flag=false;
	
	public synchronized void set(String name)
	{
		if(flag){
			try {
				this.wait();//如果标记为真,进入线程池等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		this.name=name+count;
		count++;
		System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
		flag=true;
		notify();/唤醒使用同一个锁中的一个线程
		
	}
	
	public synchronized void out(){
		if(!flag){
			try {
				this.wait();//如果标记为假,进入线程池等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+"..消费者.."+this.name);
		flag=false;
		notify();
	}
}

class Producer implements Runnable{
	Resouce resouce;
	public Producer(Resouce resouce){
		this.resouce=resouce;
	}
	public void run(){
		while(true){
			resouce.set("烤鸭");
		}
	}
}

class Customer implements Runnable{
	Resouce resouce;
	public Customer(Resouce resouce) {
		// TODO Auto-generated constructor stub
		this.resouce=resouce;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			resouce.out();
		}
		
	}
	
}
这是一个生产者,一个消费者的情况下,我们使用了一个标记flag,执行任务的时候先判断一下标记,防止生产者或者消费者连续的拿到执行权,那样会出现生产了没消费或者同一只烤鸭被消费多次的情况。有兴趣的读者可以去试一下,将flag和wait语句拿掉。我们看一下这个程序运行的结果是很和谐的。


将上面主线程中的注释行取消掉,也就是开启两个生产者,两个消费者,我们再来看会发生什么情况。如下图,出现了连续生产两次的情况,为什么会出现这个呢?


原因就在于如果某种情况(这种情况是很容易出现的)下生产者线程t0,t1都进入了线程池等待,也就是在wait语句处处于冻结状态了,等到他们被唤醒的时候将不在判断flag条件,而是直接运行下面的代码,这样就会出现上面的这种情况。所以想要让它醒来之后继续回去判断flag条件的话,我们想到了把if改成while(),我们来看一下会发生上面情况。


程序直接死锁了。。。我们来分析一下为什么会这样。假设一种情景,t0执行完之后,再次拿到执行权,这时候flag为真,t0进入冻结状态,t1拿到执行权,判断标记,进入冻结状态,t2拿到执行权,消费一次,唤醒了t0,但是t2继续持有执行权,t2判断标记为假,进入冻结状态,t3拿到执行权,同样进入冻结状态,这时候活着的线程就只有t0了,关键就在这边,t0生产完毕,结果它没有唤醒消费者线程,而是把同为生产者线程的t1唤醒了,这时候t0,t1都进入冻结状态。

所以,为了防止这种情况的发生我们现在只能notifyall(),将所有线程唤醒,防止出现这种死锁的情况,但明显这样唤醒所有线程是浪费资源的,解决办法我们下一篇再讲。这样程序在多消费者多生产者的情况下也正常运行了。







猜你喜欢

转载自blog.csdn.net/qq_37891064/article/details/79766032