这里主要解读生产者消费者模型的工作方式以及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版本就是其核心实现方式。