介绍
Condition是条件锁的意思,是指在获取锁之后发现当前业务场景自己无法处理,而需要等待某个条件的出现才可以继续处理时使用的一种锁。
最常见的生产者消费者模式,生产的元素都被消费完,则需要阻塞消费者,不可继续消费直到生产者生产新的后唤醒消费者;或者生产者生产过快,容器已满,则需要阻塞生产者,直到消费者消费一定数量才唤醒生产者。
Condition是一个接口,其定义如下
public interface Condition {
//阻塞当前线程,等待同一个Condition对象上的signal()/signalAll()唤醒
//该方法响应中断,如果发生中断,该方法抛出InterruptedException异常
void await() throws InterruptedException;
///阻塞当前线程,等待同一个Condition对象上的signal()/signalAll()唤醒
//与上面方法的区别是,该方法等待过程中不响应中断
void awaitUninterruptibly();
//阻塞线程,线程被阻塞指定的时间
//当线程被中断、超时或者signal()/signalAll(),都会唤醒线程
long awaitNanos(long nanosTimeout) throws InterruptedException;
//同awaitNanos()
boolean await(long time, TimeUnit unit) throws InterruptedException;
//同awaitNanos()
boolean awaitUntil(Date deadline) throws InterruptedException;
//唤醒一个等待线程
void signal();
//唤醒所有的等待线程
void signalAll();
}
AbstractQueuedSynchronizer提供了一个Condition的实现类ConditionObject,可以通过调用Lock.newCondition()获得一个Condition对象。每个Condition对象都与一个Lock对象相关,调用Condition对象的方法前必须获得对应Lock对象的锁。
Condition的作用与Object的wait()/notify()作用类似,调用await()可以阻塞当前线程,signal()/signalAll()可以唤醒其他阻塞线程。
基本使用
public class ConditionThread{
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
try {
lock.lock();
try {
System.out.println("await阻塞前");
// 等待条件
condition.await();
System.out.println("await阻塞后");
} finally {
lock.unlock();
System.out.println("await阻塞线程解锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//睡眠让await线程先拿到锁
Thread.sleep(1000);
lock.lock();
try {
System.out.println("signal唤醒前");
condition.signal();
System.out.println("signal唤醒后");
} finally {
System.out.println("signal唤醒线程解锁");
lock.unlock();
}
}
}
运行结果:
await阻塞前
signal唤醒前
signal唤醒后
signal唤醒线程解锁
await阻塞后
await阻塞线程解锁
可以看出,上方线程先拿到锁,执行到await方法后阻塞了,下方线程拿到锁,执行signal,直到下方线程执行完unlock操作,上方线程被唤醒,继续后续操作。
源码解析
AQS中有两个队列,一个为AQS同步队列,未拿到锁的线程会放入此队列中;另一个是
Condition队列,调用await会放入到Condition队列。两个有几点不同,AQS同步队列是双向的,而Condition队列是单向的;AQS同步队列的首节点是虚拟节点,而Condition队列的首节点是真实节点,参与排队。
Condition队列如下图:(AQS同步队列结构可见前文)
await
public final void await() throws InterruptedException {
//线程中断,抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//添加节点到Condition队列中,并返回该节点
Node node = addConditionWaiter();
// 完全释放当前线程获取的锁
// 因为锁是可重入的,state >= 1,所以这里要把获取的锁全部释放
int savedState = fullyRelease(node);
int interruptMode = 0;
//是否在同步队列中
//注意这是aqs同步队列,区分Condition队列
while (!isOnSyncQueue(node)) {
// 阻塞当前线程
LockSupport.park(this);
// 上面部分是调用await()时释放自己占有的锁,并阻塞自己等待条件的出现
// *************************分界线************************* //
// 下面部分是条件已经出现,尝试去获取锁
//中断线程一系列操作
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 尝试获取锁
// 如果没获取到会再次阻塞(前文对此方法有说明)
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 清除取消的节点
if (node.nextWaiter != null)
unlinkCancelledWaiters();
// 线程中断相关
if (interruptMode != 0)
//抛出InterruptedException,重新中断当前线程
//或不执行任何操作,具体取决于interruptMode值
reportInterruptAfterWait(interruptMode);
}
addConditionWaiter新建Node节点添加至Condition队列
private Node addConditionWaiter() {
//获取条件队列的尾节点
Node t = lastWaiter;
// 如果条件队列的尾节点不为初始状态,从头节点开始清除所有不为初始状态节点
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 新建一个节点,它的等待状态是CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 如果尾节点为空,则把新节点赋值给头节点(相当于初始化队列)
// 否则把新节点赋值给尾节点的nextWaiter指针
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
// 尾节点指向新节点
lastWaiter = node;
// 返回新节点
return node;
}
fullyRelease 完全释放锁
final int fullyRelease(Node node) {
boolean failed = true;
try {
//获取state,state >= 1,下面方法将state改为0表示释放锁
int savedState = getState();
//释放可重入锁,此方法上文有介绍
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
//失败将waitStatus设置为1,表示已取消的节点
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
isOnSyncQueue判断是否在AQS同步队列。
我们先说明下AQS同步队列和Condition队列中waitStatus等待状态的区别:
1、在Condition队列中,新建节点的初始等待状态是CONDITION(-2);
2、移到AQS的队列中时等待状态会更改为0(AQS同步队列节点的初始等待状态为0);
3、在AQS的队列中如果需要阻塞,会把它上一个节点的等待状态设置为SIGNAL(-1);
4、Condition队列和AQS队列中,已取消节点的等待状态设置为CANCELLED(1);
5、共享锁的时候还有另外一种等待状态叫PROPAGATE(-3)。
final boolean isOnSyncQueue(Node node) {
// 如果等待状态是CONDITION,或者前一个指针为空,返回false
// 说明还没有移到AQS的队列中
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 如果next指针有值,说明已经移到AQS的队列中了
if (node.next != null)
return true;
// 从AQS的尾节点开始往前寻找看是否可以找到当前节点
// 找到了也说明已经在AQS的队列中了
return findNodeFromTail(node);
}
findNodeFromTail 从AQS的尾节点开始往前寻找看是否可以找到当前节点
private boolean findNodeFromTail(Node node) {
Node t = tail;
//从tail尾节点往前循环
//存在node节点返回true
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
unlinkCancelledWaiters 清除取消的节点
private void unlinkCancelledWaiters() {
//取Condition队列首节点
Node t = firstWaiter;
Node trail = null;
//从首节点开始循环Condition队列
while (t != null) {
Node next = t.nextWaiter;
//waitStatus 不为初始状态,则从Condition队列中清除
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
signal
public final void signal() {
// 判断持有锁的线程不为当前线程,抛出异常
// 说明signal()要在获取锁之后执行
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//取Condition队列首元素,执行doSignal操作
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
doSignal执行唤醒操作,循环执行transferForSignal方法,直到transferForSignal方法返回true或执行到尾节点,意思从头开始循环,成功唤醒一位则退出循环
private void doSignal(Node first) {
do {
// 移到条件队列的头节点往后一位
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 相当于把头节点从队列中出队
first.nextWaiter = null;
// 转移节点到AQS队列中
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
transferForSignal将Node节点从Condition队列移动到AQS同步队列
final boolean transferForSignal(Node node) {
// 把节点的状态更改为0,也就是说即将移到AQS队列中
// 如果失败了,说明节点已经被改成取消状态
// 返回false,通过上面的循环可知会寻找下一个可用节点
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 调用AQS的入队方法把节点移到AQS的队列中
// 注意,这里enq()的返回值是node的上一个节点,也就是旧尾节点
Node p = enq(node);
// 上一个节点的等待状态
int ws = p.waitStatus;
// 如果上一个节点已取消了,或者更新状态为SIGNAL失败(也是说明上一个节点已经取消了)
// 则直接唤醒当前节点对应的线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
// 如果更新上一个节点的等待状态为SIGNAL成功了
// 则返回true,这时上面的循环不成立了,退出循环,也就是只通知了一个节点
// 此时当前节点还是阻塞状态
// 也就是说调用signal()的时候并不会真正唤醒一个节点
// 只是把节点从条件队列移到AQS队列中
return true;
}
从上述源码可知,signal()方法并不会真正唤醒一个节点,只是把节点从Condition条件队列移到AQS队列中,一直等到unlock解锁后才会真正唤醒。
signalAll
public final void signalAll() {
// 判断持有锁的线程不为当前线程,抛出异常
// 说明signal()要在获取锁之后执行
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
doSignalAll和doSignal很相似,doSignal从Condition队列头元素往后循环,成功转移一位元素则完成,而doSignalAll将所有符合条件的元素转移至AQS同步队列
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
//从头节点开始循环转移,至到最后一个元素
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
总结
await()方法的流程:
1、新建一个节点加入到条件队列中去;
2、完全释放当前线程占有的锁;
3、阻塞当前线程,并等待条件的出现;
8、条件已出现(此时节点已经移到AQS的队列中),尝试获取锁;
signal()方法的流程为:456
4、从条件队列的头节点开始寻找一个非取消状态的节点;
5、把它从条件队列移到AQS队列;
6、且只移动一个节点
7、signal线程unlock释放锁
需要注意一点:signal()方法后,最终会执行unlock()方法,此时才会真正唤醒一个节点,唤醒的这个节点如果曾经是条件节点的话又会继续执行await()方法LockSupport.park(this)下面的代码。