前言
上篇文章我们使用了wait¬ify实现阻塞队列实现了一个阻塞队列,今天我们来看看如何用ReentrantLock的Condition对象来实现阻塞队列
实践
public static void main(String[] args) {
ConditionQueue conditionQueue = new ConditionQueue(5);
for (int i = 0; i < 10; i++) {
final int a = i;
new Thread(() -> {
try {
conditionQueue.put(a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
conditionQueue.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
static class ConditionQueue{
private LinkedList<Integer> queue = new LinkedList<>();
private final Lock lock = new ReentrantLock();
//生产者
private final Condition producer = lock.newCondition();
//消费者
private final Condition consumer = lock.newCondition();
//最大容量
private int max;
ConditionQueue(int max){
this.max = max;
}
boolean isFull(){
return queue.size() == max;
}
boolean isEmpty(){
return queue.size() == 0;
}
void put(int i) throws InterruptedException {
lock.lock();
while (isFull()){
producer.await();
}
queue.add(i);
consumer.signalAll();
System.out.println("插入数据" + i);
lock.unlock();
}
Integer get() throws InterruptedException {
lock.lock();
while (isEmpty()){
consumer.await();
}
Integer s = queue.removeFirst();
producer.signalAll();
lock.unlock();
System.out.println("取出数据" + s);
return s;
}
}
那么condition是怎样实现阻塞的呢?
我们上面使用了await方法和signlAll方法
下面我们来看看这两个方法到底做了什么?
源码解析
进入源码我们可以发现lock.newCondition()其实是新建了一个ConditionObject对象,而ConditionObject是AbstractQueuedSynchronizer的一个内部类,ConditionObject本身会维护一个等待队列(这里和之前ReentrantLock的CLH队列要区分开)
await方法
首先我们来看看await方法
public final void await() throws InterruptedException {
//判断线程是否被中断
if (Thread.interrupted())
throw new InterruptedException();
//把当前的node节点加入condition的等待队列中
Node node = addConditionWaiter();
//释放当前线程占用的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断节点是否在等待队列中,这里的等待队列指的是CLH队列
//注意这里刚开始是肯定不会在等待队列中的,只有当线程被唤醒后,才可能会在CLH队列中
while (!isOnSyncQueue(node)) {
//如果不在队列中,直接挂起自己
LockSupport.park(this);
//这里是看线程有没有被interrupted过,这里分为3种情况
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//然后开始竞争锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
//清除掉所有的节点状态不等于condition的节点
unlinkCancelledWaiters();
if (interruptMode != 0)
//这里是看线程有没有被interrupted过,对当前线程做补偿或者抛出异常
reportInterruptAfterWait(interruptMode);
}
看下加入等待队列的addConditionWaiter方法,这个方法其实是把一个CONDITION状态的节点放入等待队列中,注意这里的NODE节点和ReentrantLock里的NODE节点是同一个对象
private Node addConditionWaiter() {
//队列中的最后一个节点
//这个node对象和之前CLH队列中的是一样的
Node t = lastWaiter;
if (t != null && t.waitStatus != Node.CONDITION) {
//清除掉所有的节点状态不等于condition的节点
unlinkCancelledWaiters();
//重新给t节点赋值
t = lastWaiter;
}
//创建节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//把节点放在队列的最后面
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
然后看下isOnSyncQueue方法,这个方法是判断当前节点是否在CLH队列中,如果在
final boolean isOnSyncQueue(Node node) {
//如果节点状态为condition或者node是第一个节点
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//next不等于null说明一定在等待队列中
if (node.next != null) // If has successor, it must be on queue
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.
*/
//这里就是一个兜底的,从后往前遍历CLH队列,看能不能找到当前node
return findNodeFromTail(node);
}
checkInterruptWhileWaiting方法是判断线程在被唤醒的时候,是否被中断过
private int checkInterruptWhileWaiting(Node node) {
//注意如果被signal后,waitStatus的状态是为0的
//1,没有被中断过 直接返回0
//2,在被signal前就中断 transferAfterCancelledWait返回true
//3,在signal后被中断的 transferAfterCancelledWait返回false
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
然后后面的acquireQueued方法,大家应该很熟悉了,就是之前的ReentranLock的竞争锁的方法,这里就不展开了,有兴趣的同学可以看看上一期reentrantLcok加锁解锁源码
await方法流程总结
线程在执行await方法的时候,会把自己加入到conditionObject的等待队列中,然后释放自身的锁资源,
判断自身是否在CLH队列中,这里有两种情况:
1,不在CLH队列,注意没有被signal的线程是不会在CLH队列中的,这时会直接park自己,等待被unpark
2,在CLH队列,说明此时已经被signal,然后进入下一步,进入竞争lock锁的流程
然后我们来看下signal是怎么把节点放入CLH的
signal方法解析
signal方法
public final void signal() {
//判断是否是拥有锁的线程在操作
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//队列的第一个节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
然后看下doSignal方法,如果是signalAll方法的话,这里其实就是while循环整个队列,对所有节点调用transferForSignal方法
private void doSignal(Node first) {
do {
//设置第一个节点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
//如果唤醒不成功的话,或者下一个节点不为空 就去唤醒下一个节点,直到成功唤醒一个节点为止
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
再看下transferForSignal方法
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//把当前节点的状态设置为0(等待队列中的节点状态本身都为CONDITION,在await方法中设置的)
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
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).
*/
//把当前节点放入CLH队列中,因为可能还有其他节点也需要等待唤醒
//注意这里返回的p节点,是当前node的前置节点
Node p = enq(node);
int ws = p.waitStatus;
//如果前置节点被CANCELLED,或者前置节点的状态已经被改变了则唤醒线程
//其实这里是一个保险的作用,目的就是确保前置节点的状态一定是SIGNAL,这样的话,在unlock的时候就会unpark
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//唤醒一次线程
LockSupport.unpark(node.thread);
return true;
}
signal方法总结
线程执行signal方法的时候,会先判断自身是否拥有锁,然后唤醒队列的第一个节点,唤醒时会先把节点的等待状态设置为0,然后把当前节点放入CLH队列中,等待获取锁成功
总结
Condition的阻塞队列其实是基于AbstractQueuedSynchronizer的内部类ConditionObject来实现的,
ConditionObject内部维护了一个队列,和Lock的CLH队列是相互独立的
比如上面例子中的producer和consumer,其实就可以理解为两个队列,当调用
producer.await方法时: 往producer队列中放入一个任务节点,并且阻塞自己
producer.singal方法时: 往CLH的队列中放入producer队列的第一个任务节点,然后等待抢锁成功被unpark
可以这么理解,CLH队列中不仅有producer的节点而且有consumer的节点
也就是说
Condition队列是控制线程有没有资格去抢锁
CLH队列是控制线程获取锁的顺序
希望大家不要混淆这两个队列
结尾
有啥理解不当之处希望大家能指出来,共勉