简介
传统的内置锁都只能有一个相关联的条件队列,因此多个线程可能在同一个条件队列上等待不同的条件谓词,导致使用notify时导致信号消失,或者使用notifyAll唤醒了非等待该信号类型的线程,造成了极大的开销。
因此,可以使用显式的Lock和Condition而不是内置锁和条件队列,来编写一个带有多个条件谓词的并发对象。在每个锁上可存在多个等待、条件等待可以是可中断的或不可中断的、基于限时的等待、以及公平的或非公平的队列操作。
与内置条件队列不同的是,对于每个Lock,可以由任意数量的Condition对象。Condition对象继承了相关的Lock对象的公平性,对于公平的锁,线程会按照FIFO顺序从Condition.await中释放。
public class ConditionBoundedBuffer<T> {
protected final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
@SuppressWarnings("unchecked")
private final T[] items = (T[]) new Object[19];
private int tail, head, count;
public void put(T x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await();
}
items[tail] = x;
if (++tail == items.length) {
tail = 0;
}
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
T x = items[head];
items[head] = null;
if (++head == items.length) {
head = 0;
}
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
通过将两个条件谓词分开并放到两个等待线程集中,Condition使其更容易满足单次通知的需求,signal比signalAll更高效,能够极大减少在每次缓存操作中发生的上下文切换与锁请求的次数。
与内置锁和条件队列一样,但使用显式的Lock和Condition时,也必须满足锁、条件谓词和条件变量之间的三元关系。在条件谓词中包含的变量必须由Lock来保护,并且在检查条件谓词以及调用await和signal时,必须持有Lock对象。
Condition接口
public interface Condition {
/**
* 暂停此线程直至一下四种情况发生
* 1.此Condition被signal()
* 2.此Condition被signalAll()
* 3.Thread.interrupt()
* 4.伪wakeup
* 以上情况.在能恢复方法执行时,当前线程必须要能获得锁
*/
void await() throws InterruptedException;
//跟上面类似,不过不响应中断
void awaitUninterruptibly();
//带超时时间的await(),并响应中断
long awaitNanos(long nanosTimeout) throws InterruptedException;
//带超时时间的await()
boolean await(long time, TimeUnit unit) throws InterruptedException;
//带deadline的await()
boolean awaitUntil(Date deadline) throws InterruptedException;
//唤醒某个等待在此condition的线程
void signal();
//唤醒有等待此condition的所有线程
void signalAll();
}
AQS的ConditionObject
ConditionObject的实现其实就是维护了两个队列
- condition队列:表示等待条件队列,其
waitStatus=Node.CONDITION
,由firstWaiter
和lastWaiter
两个舒心控制。 - sync队列:表示可以竞争锁的队列,这个跟AQS的独占锁队列一致,与ReentrantLock独占锁共用一个队列。
await()方法分析
该方法就是把当前线程创建一个Node加入condition队列,释放当前占有的锁之后,接着就一直循环,判断当前节点在不在sync队列队列中,如果不在,则当前线程挂起;否则,就可以重新竞争独占锁。
public final void await() throws InterruptedException {
/*如果当前线程已经被中断*/
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
/*释放当前线程持有的锁*/
int savedState = fullyRelease(node);
int interruptMode = 0;
/*通过循环,查看当前线程是否已经移入到sync队列中*/
while (!isOnSyncQueue(node)) {
/*如果没有在sync队列,那么将当前线程挂起*/
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
/*当条件满足后,当前线程被唤醒
* 现在重新获取上一步中释放的锁
* */
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
添加一个等待Node到条件队列中
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
/*如果最后一个等待节点的等待状态不是 CONDITION
* 说明已经被取消了,
* 那么则清理掉
* */
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
/*新建一个Condition状态的节点,并将其加在尾部*/
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
释放当前的state,最终还是调用tryRelease方法
final int fullyRelease(Node node) {
boolean failed = true;
try {
/*获取当前的状态值,通过release方法释放锁*/
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
判断是否在同步队列中
final boolean isOnSyncQueue(Node node) {
/*如果状态为CONDITION,说明还在condition队列中,还得循环等待*/
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
/*当执行signal后,会执行enq方法,将node加入到队列中去*/
/*有前驱节点也有后置节点,那么一定在sync队列中*/
if (node.next != null)
return true;
/*
* 其前置节点为非null,但是也不在Sync也是可能的,
* 因为CAS将其加入队列失败.所以我们需要从尾部开始遍历确保其在队列
*/
return findNodeFromTail(node);
}
从尾部查找node节点
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (; ; ) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
signal()方法分析
该方法就是把条件队列中头结点移动到sync队列中
public final void signal() {
/*如果不是独占锁模式,则抛出 非法监视器异常*/
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//将condition队列的第一个节点出队列,并将其加入AQS的锁sync队列
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
释放信号
private void doSignal(Node first) {
do {
/*将firstWaiter指向下一个节点
* 如果下一个节点为空,则将lastWaiter置为空
* */
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
/*断开头结点与其他节点的连接*/
first.nextWaiter = null;
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
将一个节点从condition队列转换到Sync队列
final boolean transferForSignal(Node node) {
/*
* 所谓的condition队列和sync队列就在于waitStatus的值
* 在这里将waitStatus改为NORMAL,如果不能改变,说明当前节点已经被取消(CANCELLED),
* 那么重新尝试下一个节点
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* 将当前节点移动到sync队列中,接着设置前驱节点等待状态,
* 如果被取消或者设置状态失败,那么唤醒该线程,让该线程重新同步,为了确保waitStatus是无害的
*/
Node p = enq(node);/*当前节点的前驱节点*/
int ws = p.waitStatus;
/*如果前驱节点取消,或修改状态失败*/
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
/*唤醒当前线程*/
LockSupport.unpark(node.thread);
return true;
}
signalAll()方法分析
唤醒所有等待此condition的所有线程
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
遍历所有节点,使其加入到Sync队列
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}