生产者消费者问题的两种写法

问题:一个固定容量的同步容器,有get方法和put方法,和size()方法。n个生产者不断往里面put,m个消费者不断从中get。
方式一:Object的wait和notify
public class TestPandC {

	public static void main(String[] args) {
		MyContainer<Object> c=new MyContainer<Object>(6);
		//3个生产者,每个人生产10个
		for(int i=0;i<3;i++) {
			Thread t=new Thread(new Runnable() {
				public void run() {
					for(int j=0;j<10;j++) {
						c.put("meat");
					}
				}			
			},"p"+i);
			t.start();
		}
		
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//5个消费者,每个人吃5个
		for(int i=0;i<5;i++) {
			Thread t=new Thread(new Runnable() {
				public void run() {
					for(int j=0;j<5;j++) {
						c.get();
					}
				}
			},"c"+i);
			t.start();
		}

	}

}
class MyContainer<T>{
	final private LinkedList<T> list=new LinkedList<>();
 	final private int column;
	private int size=0;

	
	MyContainer(int num){
		column=num;
	}
	public synchronized void put(T t) {
		while(size==column) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+"生产第"+size+"个");
		list.add(t);
		size++;
		notifyAll();//为什么用notifyAll(),不用notify()
	}
	
	public synchronized T get() {
		while(size==0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//叫醒后,并且抢到锁后,从这里开始执行
		System.out.println(Thread.currentThread().getName()+"开始消费第"+size+"个");
		size--;
		notifyAll();
		return list.removeFirst();
	}
	public /*synchronized*/ int size() {
		return size;
	}
}

这种方式有两个重点:

  1. 为什么wait外面用while不能用if?
    因为wait()是会释放锁的,那么即使后来,被消费者叫醒,它也是没有锁的,需要重新去抢锁。既然有多个生产者,多个生产者都会去抢,假设其中一个抢到了,并且把容器又做满了,那么其他生产者再抢到时,如果是if,那么它不会再去判断了,而是继续执行wait()后面的代码,这样可能就超了。所以要用while,wait醒来抢到锁之后还要再检查一遍有没有被其他被叫醒的生产者填满。
    这里即使用if double check也是一样不行。因为if(size==column)那还得重新wait,醒来又是和上面一样的问题了。
    wait()在99%的情况下都是和while搭配!

  2. 为什么要用notifyAll,而不能用notify()
    因为notify是随机叫醒一个线程,假如说现在容器里没有东西了,生产者生产了一个,叫醒了一个消费者,这个消费者消费了,然后又没了,去notify,但是这时叫醒的是另外一个消费者,这个消费者一看没有就会一直等,也不会再叫醒其他人了。就会陷入死循环。
    尽量用notifyAll(),少用notify()(除非个别情况,如只有一个线程)

方式二:Lock和Condition的await和signal
public class TestPandC2 {

	public static void main(String[] args) {
		Lock lock=new ReentrantLock();
		Condition producer=lock.newCondition();
		Condition consumer=lock.newCondition();
		
		MyContainer2<Object> c=new MyContainer2<Object>(6);
		//3个生产者,每个人生产10个
		for(int i=0;i<3;i++) {
			Thread t=new Thread(new Runnable() {
				public void run() {
					for(int j=0;j<10;j++) {
						c.put("meat");
					}
				}			
			},"p"+i);
			t.start();
		}
		
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//5个消费者,每个人吃5个
		for(int i=0;i<5;i++) {
			Thread t=new Thread(new Runnable() {
				public void run() {
					for(int j=0;j<5;j++) {
						c.get();
					}
				}
			},"c"+i);
			t.start();
		}


	}

}

class MyContainer2<T>{
	final private LinkedList<T> list=new LinkedList<>();
	private int size=0;
	final private int column;
	Lock lock=new ReentrantLock();
	Condition producer=lock.newCondition();
	Condition consumer=lock.newCondition();
	
	MyContainer2(int num){
		column=num;
	}
	public void put(T t) {
		lock.lock();
		while(size==column) {
			try {
//让当前线程去等待。通过producer 停的,到时候就要再通过producer来叫醒
				producer.await();
				//我的理解就是向producer这个Condition注册一下,当叫醒时,先通知producer,producer在通知它的线程
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+"生产第"+size+"个");
		list.add(t);
		size++;
		consumer.signalAll();//可以更精确控制叫醒的线程
		lock.unlock();
	}
	
	public T get() {
		lock.lock();
		while(size==0) {
			try {
				consumer.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//叫醒后,并且抢到锁后,从这里开始执行
		System.out.println(Thread.currentThread().getName()+"开始消费第"+size+"个");
		size--;
		producer.signalAll();
		lock.unlock();
		return list.removeFirst();		
	}
	public /*synchronized*/ int size() {
		return size;
	}
}

借用别人的一张图:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值