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

这里主要解读生产者消费者模型的工作方式以及JDK5.0新增实现和传统同步关键字实现方式的区别。

在JDK5.0新增了Lock、Condition这两个接口来处理多线程安全问题。

Lock:可替代synchronized实现的同步函数或同步代码块。

Condition:封装 Object 监视器方法(wait、notify 和 notifyAll)成为专用的监视器对象, Lock 可以绑定多组监视器对象.

这俩兄弟的出世带来了什么利好呢?

Lock可以绑定多组Condition最重要的好处应该是,通过对Condition对象的分组使用(一组监视消费者,一组监视生产者)可以达到只唤醒对方线程的目的,而不必使用

notifyAll不分青红皂白唤醒所有线程。

API里给出了一个很好的示例(在这里

 

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }


这里实现了一个线程安全的缓冲区,下面对它进行一番演义。

 

 

/**
 * BoundedBuffer2 诸葛亮率领的蜀汉军队的粮仓(2.0版本)
 */
public class BoundedBuffer2 {
		// lock-粮草官 :主要职责 督查筹粮分配粮食;主要装备:高音大喇叭
		final Lock lock = new ReentrantLock();

		/*
		 * Condition:诸葛武侯引进的黑科技:两路信号手动切换多功能高音大喇叭
		 * 
		 * notFull (筹粮队信号):粮草官发现粮仓没装满时
		 * 拿起他的大喇叭打开通向筹粮兵这路信号将响起(只有负责筹粮的士兵能接收到此路信号,所以只唤醒负责筹粮的士兵):
		 * “丫的粮仓还没装满!去弄粮食,接丞相令是要渭水分兵屯田的,屯田不行去找刘禅要呀,要不来去曹魏那抢呀!”
		 * 
		 * notEmpty(火头军信号):
		 * 粮草官发现粮仓已经有粮食的时候(不是空的了)拿起他的大喇叭向火头军喊话(只有他们能接收到):“喂,喂!有粮食了”。
		 */
		final Condition notFull = lock.newCondition();
		final Condition notEmpty = lock.newCondition();

		final Object[] items = new Object[100];// 粮仓总共能装100万石粮食,够蜀汉十万大军吃十个月了。
		int putptr, takeptr, count;

		public void put(Object x) throws InterruptedException {// 筹粮的具体运作方式
			lock.lock();// 首先粮官就位,开始监工(入库)嘛!
			try {
				while (count == items.length)
					// 一旦粮仓装满
					notFull.await();// 粮官大喇叭响起(通向正在筹粮兵信号  如果有多个筹粮队伍(生产者线程)也只是通知正在工作的这一个  ):
																		//"正在筹粮士兵全线 立刻 就地 停止工作 等待通知!"。
				// 没满的话就筹粮嘛,putptr大粮仓内带有编号的小粮仓一百个,从0到99按顺序往里装嘛
				items[putptr] = x;
				System.out.println(Thread.currentThread().getName()+"---"+putptr+"号小粮仓  运---进 粮食,现存粮食共计:"+(count+1)+"小仓!");
				if (++putptr == items.length)// 装到99号小粮仓了咋办?
					// 从0号开始继续装嘛。列为看官可能有问题了 0号不是有粮食吗?
					// 没有了,筹粮的同时被火头军运走做饭消耗了。(循环队列)
					putptr = 0;
				++count;// 计数,新增了一小粮仓粮食
				notEmpty.signal();// 粮官大喇叭响起(通向已经休息的火头军中的一支):粮仓已经有粮食了,可以来取了!
			} finally {
				lock.unlock();// 这一小仓入库完毕 ,粮官休息去了!
			}
		}

		public Object take() throws InterruptedException {
			lock.lock();// 首先粮官就位,开始监工(出库)嘛!
			try {
				while (count == 0)
					// 一旦粮仓一颗粮食都没有
					notEmpty.await();// 粮官大喇叭响起(通向来取粮的火头军):粮仓没有粮食了,不要来取了!
				// 粮仓没空的话就取出粮食,takeptr 大粮仓内带有编号的小粮仓一百个,从0到99按顺序往外取出粮食
				Object x = items[takeptr];
				System.out.println(Thread.currentThread().getName()+"---"+takeptr+"号小粮仓  运出  粮食,现存粮食共计:"+(count-1)+"小仓!");
				if (++takeptr == items.length)// 取到99号小粮仓了
					takeptr = 0;// 从0号开始继续取
				--count;// 计数,减少了一小粮仓粮食
				notFull.signal();// 粮官大喇叭响起(通向已休息的筹粮兵中的一支):喂!喂!筹粮士兵开工,粮仓不满了,有空位了,渣渣!
				return x;// 这一小仓运出库
			} finally {
				lock.unlock();// 这一小出入库完毕 ,粮官休息去了!
			}
		}
	}

 

这里缓冲区是一个阻塞的循环队列,存取粮食的过程如下:

测试一下:

 

/**
 * 封装生产者任务:负责筹粮
 * 
 */
class Producer implements Runnable {
	private BoundedBuffer2 buffer;

	public Producer(BoundedBuffer2 buffer) {
		this.buffer = buffer;

	}

	public void run() {
		int i = 0;

		try {
			while (true) {
				// Thread.sleep(1);
				buffer.put(new Object());

			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

/**
 * 封装消费者任务:负责取粮
 * 
 */
class Consumer implements Runnable {
	private BoundedBuffer2 buffer;

	public Consumer(BoundedBuffer2 buffer) {
		this.buffer = buffer;
	}

	public void run() {
		int i = 0;

		try {
			while (true) {
				// Thread.sleep(1);
				buffer.take();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

public class MultithreadTest {
	public static void main(String[] args) {
		BoundedBuffer2 buffer = new BoundedBuffer2();
		Producer pdc = new Producer(buffer);
		Consumer csm = new Consumer(buffer);
		Thread pThread1 = new Thread(pdc);
		Thread pThread2 = new Thread(pdc);
		Thread cThread1 = new Thread(csm);
		Thread cThread2 = new Thread(csm);
		pThread1.start();
		pThread2.start();
		cThread1.start();
		cThread2.start();
	}
}


运行现象:

 


以上是JDK5.0后的Lock和Condition的实现方式,下面看一下synchronized 实现主要注释不同之处,这里用同步方法,用同步代码块也是可以。

 

/**
 * BoundedBuffer 诸葛亮率领的蜀汉军队的粮仓(1.0版本)
 */
public class BoundedBuffer {
	// this-粮草官 :主要职责 督查筹粮分配粮食;主要装备:无,靠吼!
	final Object lock = new Object();
	final Object[] items = new Object[100];// 粮仓总共能装100万石粮食,够蜀汉十万大军吃十个月了。
	int putptr, takeptr, count;

	public synchronized void put(Object x) throws InterruptedException {
		while (count == items.length)
			this.wait();// 粮官扯着嗓子吼起(来到粮草的兵,不分什么兵都会接收到):正在筹粮士兵全线 立刻 就地 停止工作 等待通知
		items[putptr] = x;
		System.out.println(Thread.currentThread().getName() + "---" + putptr
				+ "号小粮仓  运---进 粮食,现存粮食共计:" + (count + 1) + "小仓!");
		if (++putptr == items.length)
			putptr = 0;
		++count;
		this.notifyAll();// 粮官吼起(通向所有已经休息的小部队):粮仓已经有粮食了,可以来取了!
	}

	public synchronized Object take() throws InterruptedException {
		while (count == 0)
			this.wait();// 粮官扯着嗓子吼起(来到粮草的兵,不分什么兵都会接收到):粮仓没有粮食了,不要来取了!
		Object x = items[takeptr];
		System.out.println(Thread.currentThread().getName() + "---" + takeptr
				+ "号小粮仓  运出  粮食,现存粮食共计:" + (count - 1) + "小仓!");
		if (++takeptr == items.length)
			takeptr = 0;
		--count;
		this.notifyAll();// 粮官大喇叭响起(通向筹粮兵):喂!喂!筹粮士兵全线开工,粮仓不满了,有空位了,渣渣!
		return x;
	}
}

 

小结:通过2.0版本的粮草官持有高科技喇叭可以指定唤醒筹粮队或者是火头军,而不像1.0版本那样不能区分军种在需要筹粮时把火头军也唤醒了。

这就是Lock、Condition这对组合相较与synchronized 实现方式的优势。另外ArrayBlockingQueue这个类提供了线程安全有界缓存区功能,有需要可以使用这个类,本篇中2.0版本就是其核心实现方式。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值