Java锁Lock源码分析(⼆)条件锁
本篇博⽂主要分析条件锁的源码实现、以及状态两个队列的变化:
1)Condition的使⽤场景
2)lock⽅法的队列(FIFO双向⽆环链表)官⽅点说是同步队列 sync queue
3)condition队列(FIFO单向队列) 官⽅点说是条件队列 condition queue
4) await和signal⽅法被调⽤两个队列的变化图
本⽂是依赖于上篇博⽂在阅读本⽂之前强烈推荐先看下上⼀篇。
上篇主要主要是讲述了Lock的⼏个要点:
1. state>0表⽰当前线程持有了锁,以及重⼊锁是如何表⽰的
2)并发的三个线程,获取锁过程、⾃旋的过程以及Node.waitStatus的状态图
3)源码讲解volatile、等待队列(FIFO的双向⽆环链表)的⼊队和出队
有了上⼀篇的基础再读本篇博⽂事半功倍,在说⼀点,本节主要以图的形式展开(以为代码很简答就是两个对列,准确来说是⼀个双向⽆环链表和⼀个单项⽆环链表的⼊队和出队操作,主要以图的形式看下就可以了)。
Condition跟Object的相似之处:
condition.await()类⽐Object的wait()⽅法,
condition.signal()类⽐Object的notify()⽅法,
conditionsignalAll()类⽐Object的notifyAll()⽅法。
先获取到锁,然后在判断条件是否满⾜,不满⾜则挂起,等待被唤醒
不同之处在于Object中的这些⽅法是需要跟同步监视器synchronized联合使⽤,⽽Condition是Lock配合使⽤。
Condition能更细粒度的控制线程的休眠与唤醒,对于同⼀个锁,我们可以创建多个Condition,来完成⽣产者和消费者的业务场景。
摘⾃ Doug Lea 的例⼦(⽹上到处是我直接拷贝过来),为了更好的说明我们本次以4个线程 thread1/thread2/thread3/thread4 来展开源码的分析。
import urrent.locks.Condition;
import urrent.locks.Lock;
import urrent.locks.ReentrantLock;
class BoundedBuffer {
2阶魔方
final Lock lock = new ReentrantLock();
脚趾长痣// condition 依赖于 lock 来产⽣
final Condition notFull = wCondition();
final Condition notEmpty = wCondition();若隐若现近义词
final Object[] items = new Object[100];
int putptr, takeptr, count;
// ⽣产
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 队列已满,等待,直到 not full 才能继续⽣产
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal(); // ⽣产成功,队列已经 not empty 了,发个通知出去
} finally {
lock.unlock();
}
}
// 消费
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 队列为空,等待,直到队列 not empty,才能继续消费
科长Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
-
-count;
notFull.signal(); // 被我消费掉⼀个,队列 not full 了,发个通知出去
return x;
} finally {
lock.unlock();
毛桃图片}
}
}
这了已经展⽰了条件锁的使⽤,就是先lock()获取锁成功,让后再while(⼀定是while判断不能是if判断,不然只要不满⾜就永远不满⾜条件了)判断,不满⾜条件则condition.await();等待唤醒在此进⼊while判断条件,获取执⾏机会。
当向缓冲区中写⼊数据之后,唤醒”读线程”;
当从缓冲区读出数据之后,唤醒”写线程”,并且当缓冲区满的时候,”写线程”需要等待;
当缓冲区为空时,”读线程”需要等待。
如果采⽤Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写⼊数据之后需要唤醒”读线程”时,不可能通过notify()或notifyAll()明确的指定唤醒”读线程”,⽽只能通过notifyAll唤醒所有线程(但是notifyAll⽆法区分唤醒的线程是读线程,还是写线程)。 但是,通过Condition,就能明确的指定唤醒读线程。
不知道⼤家还是是否有印象AQS的内部类Node节点:
现在⼜多了⼀个内部类ConditionObject,他是在成功获取锁了之后再看是否满⾜条件,不满⾜再次进⼊条件队列阻塞,满⾜的话执⾏相应的业务逻辑,两个对列是如何交互的下⽂会将。
再讲之前我们先模拟如下场景:纸艺手工制作
我们假定4个线程的并发执⾏lock.lock()⽅法执⾏的某个时间状态是如下的:
thread1成功获取了锁【lock()返回】,但是数组已经满了执⾏condition.await()【await()⽅法会将⾃⼰加⼊到条件队列并释放所后边代码会说明】,并将thread1挂起。
thread2在thread1释放锁后成功获取了锁【lock()返回】,数组也是满了执⾏condition.await(),正在释放锁,即thread2正在调⽤fullRelea()⽅法。
thread3和thread4都获取锁失败阻塞在lock⽅法中,在等待队列中。
lock()⽅法的在上⼀篇⽂章写过很详细,这⾥直接跳过看await()⽅法,它是AQS的另⼀个内部类ConditionObject的⽅法:
addConditionWaiter()就是创建⼀个条件队列的节点,
new Node(thread,Node.CONDITION)将waitStatus设置为-2,代码如下:
private Node addConditionWaiter() {
冬至扫墓Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
el
lastWaiter = node;
return node;
}
这两个截图和上篇 就能得出如下的4个线程的状态:
thread2和thread3都是:
获取到了锁AQS的state>1
不满⾜执⾏业务逻辑条件创建条件Node,释放锁state归为0我们thread2释放锁:
final int fullyRelea(Node node) {鱿鱼卷
boolean failed = true;
try {
int savedState = getState();
if (relea(savedState)) {
failed = fal;
return savedState;
} el {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
public final boolean relea(int arg) {
if (tryRelea(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return fal;
}