多线程——生产者消费者模型

写一个固定容量同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用。
1、synchronized同步方法

public class Container1<T> {
	final private LinkedList<T> lists = new LinkedList<>();
	final private int MAX = 10; //最多10个元素
	private int count = 0;
    //生产者
	public synchronized void put(T t) {
		while(lists.size() == MAX) { //使用while不断判断是否等于MAX值,当数量达到MAX值后使得生产线程阻塞,被消费一个便立刻结束循环唤醒生产线程
			try {
				this.wait(); //effective java
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		lists.add(t);
		++count;
		this.notifyAll(); //֪ͨ通知消费者线程进行消费
	}
    //消费者
	public synchronized T get() {
		T t = null;
		while(lists.size() == 0) {//一旦数量等于0,便使消费线程阻塞
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		t = lists.removeFirst();
		count --;
		this.notifyAll(); //通知生产者线程进行生产
		return t;
	}
	public static void main(String[] args) {
		Container1<String> c = new Container1<>();
		//启动消费者线程
		for(int i=0; i<10; i++) {
			new Thread(()->{
				for(int j=0; j<5; j++) System.out.println(c.get());
			}, "c" + i).start();
		}
		try {
			Thread.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//启动生产者线程
		for(int i=0; i<2; i++) {
			new Thread(()->{
				for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
			}, "p" + i).start();
		}
	}
}

程序中定义了MAX变量来限制产品的总数,定义了count变量用来判断生产了几个 产品和消费了几个 产品,在put()方法中,首先判断 LinkedList集合中产品是否是MAX变量的值,如果是启动所有消费者线程,反之开始生产 产品,在get()方法中,首先判断是否还有 产品,也就是MAX的值是否为0,如果为0通知所有生产者线程开始生产 产品,反之不为0 产品数就继续减少,需要注意的点是,这里我们加了synchronized,因为++count生产产品变成新值时,一个线程还没来得及加的时候,咋们的count还为老值,另外一个线程读到的值很可能还不是最新值,所以不加锁就会出问题,main方法中通过for循环分别创建了2个生产者线程生产分别生产25个 产品,也就是50个产品,10个消费者线程每个消费者消费5个 产品,也就是50个 产品,首先启动消费者线程,然后启动生产者线程。

在判断容器中为空或者满的时候为什么用while而不是用if? 因为当LinkedList集合中产品数等于最大值的时,if在判断了集合的大小等于MAX的时候,调用了wait()方法以后,它不会再去判断一次,方法会继续往下运行,假如在你wait()以后,另一个方法又添加了一个产品,你没有再次判断,就又添加了一次,造成数据错误,就会出问题,因此必须用while。

注意看我们用的是notifyAll()来唤醒线程的,notifyAll()方法会叫醒等待队列的所有方法,那么我们都知道,用了锁以后就只有一个线程在运行,其他线程都得wait(),不管你有多少个线程,这个时候被叫醒的线程有消费者的线程和生产者的线程,所有的线程都会争抢这把锁,比如说我们是生产者线程,生产满了,满了以后我们叫醒消费者线程,可是很不幸的是,它同样的也会叫醒另外一个生产者线程,假如这个生产者线程拿到了刚才第一个生产者释放的这把锁,它又wait()一遍,wait()完以后,又叫醒全部的线程,然后又开始争抢这把锁,其实从这个意义上来讲,生产者的线程wait时,没有必要去叫醒别的生产者。

所以基于这样的考虑,我们生产者只叫醒消费者线程,消费者线程只负责叫醒生产者线程。
2、ReentrantLock锁

public class Container2<T> {
	final private LinkedList<T> lists = new LinkedList<>();
	final private int MAX = 10; //最多10个元素
	private int count = 0;
     private Lock lock = new ReentrantLock();
     //两个Condition,即两个同步队列
	private Condition producer = lock.newCondition();
	private Condition consumer = lock.newCondition();
	public void put(T t) {
		try {
			lock.lock();
			while(lists.size() == MAX) {
				producer.await();
			}
			lists.add(t);
			++count;
			consumer.signalAll(); //通知消费者线程进行消费
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	public T get() {
		T t = null;
		try {
			lock.lock();
			while(lists.size() == 0) {
				consumer.await();
			}
			t = lists.removeFirst();
			count --;
			producer.signalAll(); //通知生产者进行生产
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return t;
	}
	public static void main(String[] args) {
		Container2<String> c = new Container2<>();
		//启动消费者线程
		for(int i=0; i<10; i++) {
			new Thread(()->{
				for(int j=0; j<5; j++) System.out.println(c.get());
			}, "c" + i).start();
		}
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//启动生产者线程
		for(int i=0; i<2; i++) {
			new Thread(()->{
				for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
			}, "p" + i).start();
		}
	}
}

使用ReentrantLock与synchronized最大区别在于ReentrantLock它可以有两种Condition条件,即会有两个同步队列,一个是成产线程的同步队列,一个是消费者的同步队列,一但数量达到MAX峰值的时候调用producer.await(),使得生产线程全部阻塞,调用consumer.signalAll()唤醒全部消费者线程,也就是说我在producer的情况下阻塞的,我叫醒的只是consumer,同理在消费者线程使得数量为0的时候,我阻塞的全是消费者线程,叫醒的全是生产者线程,这就是ReentrantLock的含义,它能够精确的指定哪些线程被叫醒,我们来说一下Lock和Condition的本质是什么,在synchronized里调用wait()和notify()的时候,它只有一个等待队列,而lock.newnewCondition()的时候,有多少个Condition,则创建了多少个等待队列,Condition的本质就是等待队列个数,以前只有一个等待队列,现在我new了两个Condition,一个叫producer等待队列,另一个叫consumer的等待队列,当我们使用producer.await();的时候,指的是阻塞producer的等待队列中的线程,使用producer.signalAll()指的是唤醒producer这个等待队列的线程,consumner也是如此,在生产者线程里叫醒consumer等待队列的线程也就是消费者线程,在消费者线程里叫醒producer待队列的线程也就是生产者线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值