Condition
任何一个Java 对象,都拥有一组监视器方法(定义在java.lang.Object 上),主要包括wait(),wait(long timeOut),notify()以及notifyAll()方法。这些方法与synchronized 同步关键字配合,可以实现等待/通知模式。Condition 接口也提供了类似Object 的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式已经功能特征上还是有差别的。
通过对比Object的监视器方法和Condition接口,可以更详细地了解Condition的特性,对比项与结果如下表。
Condition 使用
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BoundedQueue<T> {
// 线程池
private static ExecutorService THREAD_POOL = new ThreadPoolExecutor(2, 4, 60, TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(1000), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
private Object[] items; // 对象数组
// 添加的下标,删除的下标和数组当前数量
private int addIndex, removeIndex, count;
private Lock lock = new ReentrantLock(); // 定义一个可重入锁
private Condition notEmpty = lock.newCondition(); // 添加一个Condition
private Condition notFull = lock.newCondition(); // 添加一个Condition
public BoundedQueue(int size) {
items = new Object[size];
}
/**
* 添加一个元素,如果数组满,则添加线程进入等待状态,直到有"空位"
* @param t
* @throws InterruptedException
*/
public void add(T t) throws InterruptedException {
lock.lock(); // 获取锁
try {
while (count == items.length) { // 如果数组满了,notFull进入等待
System.out.println("items满了,add方法进入等待.");
notFull.await(); // 等待remove方法里的notFull.signal()
}
items[addIndex] = t; // item添加对象
if (++addIndex == items.length) // 调整数组索引,避免越界
addIndex = 0;
++count; // count+1,代表添加了一个对象
notEmpty.signal(); // 走到这里,数组里至少有1个对象,必不为空,因此唤醒notEmpty
} finally {
System.out.println("add: " + t);
lock.unlock(); // 释放锁
}
}
/**
* 由头部删除一个元素,如果数组空,则删除线程进入等待状态,
* 直到有新添加元素(注意这里并没有真的删除元素,只是把count-1当作是删除)
* @return
* @throws InterruptedException
*/
@SuppressWarnings("unchecked")
public T remove() throws InterruptedException {
lock.lock(); // 获取锁
try {
while (count == 0) { // 如果数组为空,notEmpty进入等待
System.out.println("items为空,remove方法进入等待.");
notEmpty.await(); // 等待add方法里的notEmpty.signal()
}
Object x = items[removeIndex]; // item移除对象(假移除)
if (++removeIndex == items.length) // 调整数组索引,避免越界
removeIndex = 0;
--count; // count-1,代表移除了一个对象
notFull.signal(); // 走到这里,数组里至少有1个空位,必不为满,因此唤醒notFull
return (T) x;
} finally {
System.out.println("remove");
lock.unlock(); // 释放锁
}
}
public static void main(String args[]) throws InterruptedException {
int count = 3; // 可以加大数组的size来看更多的过程
BoundedQueue<Integer> bq = new BoundedQueue<Integer>(count);
// 开启一个线程执行添加操作
THREAD_POOL.submit(new Callable<Object>() {
public Object call() throws InterruptedException {
for (int i = 0; i < count * 2; i++) {
bq.add(i);
Thread.sleep(200); // 通过睡眠来制造添加和移除的速度差
}
return null;
}
});
// 开启一个线程执行移除操作
THREAD_POOL.submit(new Callable<Object>() {
public Object call() throws InterruptedException {
Thread.sleep(1000);
for (int i = 0; i < count * 2; i++) {
bq.remove();
Thread.sleep(50); // 通过睡眠来制造添加和移除的速度差
}
return null;
}
});
}
}
Condition 基础属性
//条件队列的头节点
private transient Node firstWaiter;
//条件队列的尾节点
private transient Node lastWaiter;
Condition 数据结构
await 方法
public final void await() throws InterruptedException {
if (Thread.interrupted())//判断当前线程是否中断,如果被中断则抛出异常
throw new InterruptedException();
Node node = addConditionWaiter(); // 添加一个waitStatus为CONDITION的节点到条件队列尾部
int savedState = fullyRelease(node);//释放锁 这里会考虑重入锁的释放,并且返回重入次数。
int interruptMode = 0;
while (!isOnSyncQueue(node)) {//判断node 是否在同步队列中
LockSupport.park(this);//阻塞当前线程(当其他线程调用signal()方法时,该线程会从这个位置去执行)
//要判断当前被阻塞的线程是否是因为interrupt()唤醒
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//重新竞争锁,savedState表示的是被释放的锁的重入次数.
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters(); // 移除waitStatus为CANCELLED的节点
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
addConditionWaiter 方法
private Node addConditionWaiter() { //将获取锁的线程加入等待队列
Node t = lastWaiter; //尾节点设置为 t
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters(); // 移除waitStatus不为CONDITION的节点(条件队列里的节点waitStatus都为CONDITION)
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);//创建waitStatus=CONDITION 的节点
if (t == null) // t为空,代表条件队列为空
firstWaiter = node;//将头节点设置node
else
t.nextWaiter = node;//否则,队列不为空。将t(原尾节点)的后继节点赋值为node
lastWaiter = node;// 将node赋值给尾节点,即将node放到条件队列的尾部。这里没有用CAS来保证原子性,原因在于调用await()方法的线程必定是获取了锁的线程,也就是说该过程是由锁来保证线程安全的
return node;
}
fullyRelease 方法
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();//获取锁状态标记位,包括锁重入
if (release(savedState)) {//释放锁
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED; // 如果release失败则将该节点的waitStatus设置为CANCELLED
}
}
isOnSyncQueue 方法
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null) // 如果waitStatus为CONDITION 或 node没有前驱节点,则必然不在同步队列,直接返回false
return false;
if (node.next != null) // If has successor, it must be on queue 如果有后继节点,必然是在同步队列中,返回true
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node); // 自旋循环同步队列 判断node是否为同步队列节点,如果是返回true,否则返回false
}
signal 方法
public final void signal() {
if (!isHeldExclusively()) //判断释放锁的线程是否获得锁 ,如果未获得锁则抛出异常
throw new IllegalMonitorStateException();
Node first = firstWaiter; //头节点设置为first
if (first != null)//判断头节点是否为空
doSignal(first); // 唤醒条件队列的头节点
}
doSignal 方法
private void doSignal(Node first) { // 将条件队列的头节点移到同步队列
do {
if ( (firstWaiter = first.nextWaiter) == null) // 将first节点赋值为first节点的后继节点(相当于移除first节点),如果first节点的后继节点为空,则将lastWaiter赋值为null
lastWaiter = null;
first.nextWaiter = null; // 断开first节点对first节点后继节点的关联
} while (!transferForSignal(first) && // transferForSignal:将first节点从条件队列移动到同步队列
(first = firstWaiter) != null); // 如果transferForSignal失败,并且first节点不为null,则向下遍历条件队列的节点,直到节点成功移动到同步队列 或者 firstWaiter为null
}
transferForSignal 方法
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) //利用CAS node waitStatus 改初始状态 修改失败则移动到同步队列失败
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);//将node 添加到同步队列
int ws = p.waitStatus; //获取node 节前驱节点等待状态
// 如果node前驱节点的状态为CANCELLED(ws>0) 或 使用CAS将waitStatus修改成SIGNAL失败,则代表node的前驱节点无法来唤醒node节点,因此直接调用LockSupport.unpark方法唤醒node节点
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
总结
- 调用await和signal方法都需要先获得锁,否则会抛异常。
- 调用await方法会新建一个waitStatus为CONDITION、线程为当前线程的节点到条件队列尾部,然后当前线程会释放掉锁,并进入阻塞状态,直到该节点被移到同步队列或者被中断。该节点被移动到同步队列,并不代表该节点线程能立马获得锁,还是需要在同步队列中排队并在必要时候(前驱节点为head)调用tryAcquire方法去获取,如果获取成功则代表获得了锁。
- 调用signal方法会将条件队列的头节点移动到同步队列。
参考文献
《java 并发编程的艺术》