在JVM系列博客http://yizhenn.iteye.com/blog/2290864中讲过,Java语言的同步机制在底层实现上只有两种手段:"互斥"和"协同".体现在Java语言层面上,就是内置锁和内置条件队列.内置锁即synchronized,这个相关的博客有很多,不再多讲.内置条件队列这个词大家或许没有听过,他指的是Object.wait(),Object.notify(),Object.notifyAll()三种方法(关于这三种方法请参考博客http://yizhenn.iteye.com/blog/2290864).原来是这么回事!那为何叫条件队列呢?下面就来讲讲原因. 首先队列大家一定都清楚,一种先进先出的数据结构.正常的队列中存储的都是对象,而条件队列中存储的是"处于等待状态的线程",这些线程在等待某种特定的条件变成真。正如每个Java对象都可以作为一个锁,每个对象同样可以作为一个条件队列,这个对象的wait,notify,notifgAll就构成了内部条件队列的API。对象的内置锁与条件队列是相互关联的。要调用条件队列的任何一个方法,必须先持有该对对象上的锁。没懂的话多读几遍,慢慢理解。 我们都知道,线程是用来干活的,没人想让线程始终处于等待状态.因此"条件队列中的线程一定是执行不下去了才处于等待状态",这个"执行不下去的条件"叫做"条件谓词". 至此,我们知道了3个重要的概念:锁,条件谓词,条件队列。虽然他们的关系并不复杂,但是一定要注意,wait()方法的返回并不一定意味着正在等待的条件谓词变成真了。举个列子: 假设现在有三个线程在等待同一个条件谓词变成真,然后另外一个线程调用了notifyAll()方法。此时,只能有一个线程离开条件队列,另外两个线程将仍然需要处于等待状态,这就是 在代码中使用while(conditioin is not true){this.wait();}而不使用if(condition id not true){this.wait();}的原因。另外一种情况是:同一个条件队列与多个条件谓词互相关联。 这个时候,当调用此条件队列的notifyAll()方法时,某些条件谓词根本就不会变成真。 在本文的例子中,可以看到使用while而不是if来判断条件谓词是否为空,就是基于以上几种原因的考虑。我们用一个句话来概括:“每当线程被从wait中唤醒时,都必须再次测试条件谓词”,切记,下面是条件等待的标准形式:
void xxxMethod() throws InterruptedException{
synchronized(lock){
while(!conditionPredition)
lock.wait();
doSomething();
}
}
先来看一个不使用条件队列的例子,这个例子实现了一个有界的缓存并通过轮询和休眠的形式来实现简单的阻塞。
public abstract class BaseBoundedBuffer<T> {
private Object[] buf ;
private int head,tail,count;
protected BaseBoundedBuffer(int capacity){
this.head=0;
this.tail=0;
this.count=0;
this.buf=new Object[capacity];
}
protected synchronized void doPut(T t){
buf[tail]=t;
if(++tail==buf.length)
tail=0;
++count;
}
protected synchronized T doGet(){
Object obj=buf[head];
buf[head]=null;
if(++head==buf.length)
head=0;
--count;
return (T)obj;
}
protected synchronized boolean isFull(){
return count==buf.length;
}
protected synchronized boolean isEmpty(){
return count==0;
}
}
/**
* 通过轮训的方式来实现简单的阻塞
* @author Administrator
*
* @param <T>
*/
public class SleepyBoundedBuffer<T> extends BaseBoundedBuffer<T> {
public SleepyBoundedBuffer(int capacity){
super(capacity);
}
public void put(T t) throws InterruptedException{
while(true){
synchronized(this){
if(!isFull()){
doPut(t);
return;
}
}
Thread.sleep(2000);
}
}
public T get() throws InterruptedException{
while(true){
synchronized (this) {
if(!isEmpty()){
return doGet();
}
}
Thread.sleep(2000);
}
}
}
针对这个例子,使用条件队列进行改进,如下:
public class BoundedBuffer<T> extends BaseBoundedBuffer<T> {
public BoundedBuffer(int capacity){
super(capacity);
}
public synchronized void put(T t) throws InterruptedException{
while(isFull())//执行该方法的对象在条件谓词isFull上等待
this.wait();
this.doPut(t);
this.notifyAll();
}
public synchronized T get() throws InterruptedException{
while(isEmpty())//执行该方法的对象在条件谓词isEmpty上等待
this.wait();
T t=this.doGet();
this.notifyAll();
return t;
}
}
在上面的例子中,同一个条件队列上存在两个条件谓词。这样,当调用notifyAll()方法的时候,唤醒的不仅是在isFull上等待的线程,还有在isEmpty上等待的线程,尽管唤醒在
!isEmpty上等待的线程是没有必要的,这就迫使我们想使用一种更加细颗度的条件队列。在Java中,除了提供内置锁和内置条件队列(上文所讲),还提供显式锁和显式条件队列。其中显式锁为Lock,请参考博客http://yizhenn.iteye.com/blog/2303195和博客http://yizhenn.iteye.com/blog/2303250。显示条件队列为Condition对象。
一个Condition是和一个Lock关联起来的,就像一个内置条件队列和一个内置锁关联起来一样。要创建一个Condition,可以在相关联的Lock上调用newCondition()方法。[color=red]每个内置锁只能有一个与之关联的内置条件队列,与之不同的是,每个Lock上可以有多个与他关联的Condition,这就使得我们对Condition的控制更加细粒度化/color]。 对于上面的BoundedBuffer类,使用显式条件队列进行改进,如下:(由于不再使用内置锁,所以不能继承父类BoundedBuffer了)
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionBoundedBuffer<T> {
private Lock lock=new ReentrantLock();
private Condition notFull=lock.newCondition();
private Condition notEmpty=lock.newCondition();
private int head,tail,count;
private Object[] buf;
public ConditionBoundedBuffer(int capacity){
buf=new Object[capacity];
head=0;tail=0;count=0;
}
public void add(T t) throws InterruptedException{
lock.lock();
try{
while(count==buf.length)
notFull.await();
buf[tail]=t;
if(++tail==buf.length)
tail=0;
count++;
notEmpty.signal();
}finally{
lock.unlock();
}
}
public T get() throws InterruptedException{
lock.lock();
try{
while(count==0)
notEmpty.await();
Object obj=buf[head];
buf[head]=null;
if(++head==buf.length)
head=0;
count--;
notFull.signal();
return (T)obj;
}finally{
lock.unlock();
}
}
}
特别注意:在Condition对象中,与wait,notify,notifgAll三种方法对应的分别是await,signal,signalAll。但是由于Java中所有的类都继承自Object类,因此Condition类中也存在wait,notify,notifgAll.一定要注意,需要使用await,signal,signalAll.
关于notify和notifyAll(signal和signalAll同理),需要补充说明一下。notify是从条件队列从随机选择一个线程来唤醒,而notifyAll是唤醒条件队列中的所有线程。只有同时满足下列两个条件时,才能使用单一的notify而不是notifyAll。
条件1. 所有的等待线程类型相同:只有一个条件谓词与条件队列相关,并且每个线程从wait返回后将执行相同的操作。
条件2. 单进单出:条件变量上的每次通知,最多只能唤醒一个线程来执行。
笔者开设了一个知乎live,详细的介绍的JAVA从入门到精通该如何学,学什么?