您当前的位置:网站首页>dnf补丁,吉他调音-克服消费痛点,最具性价比的车型,你值得拥有

dnf补丁,吉他调音-克服消费痛点,最具性价比的车型,你值得拥有

2019-05-16 08:36:21 投稿作者:admin 围观人数:118 评论人数:0次

在剖析 Java 并发包 java.util.concurrent 源码的时分,少不了需求了解 AbstractQueuedSynchronizer(以下简写AQS)这个抽象类,因为它是 Java 并发包的根底东西类,是完结 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等类的根底。

Google 一下 AbstractQueuedSynchronizer,咱们能够找到许多关于 AQS 的介绍,可是许多都没有介绍清楚,因为大部分文章没有把其间的一些要害的细节说清楚。

本文将从 ReentrantLock 的公正锁源码动身,剖析下 AbstractQueuedSynchronizer 这个类是怎样作业的,期望能给咱们供给一些简略的协助。

声明以下几点:

  1. 本文有点长,但仍是挺简略,首要面向读者目标为并发编程的初学者,或许想要阅览 Java 并发包源码的开发者。关于新手来说,或许需求花好几个小时才干彻底看懂,可是这时间必定是值得的。
  2. 源码环境 JDK1.7(1.8没啥改变),看到不了解或有疑问的部分,最好能自己翻开源码看看。Doug Lea 大神的代码写得诚心不错。
  3. 本文不剖析同享办法,这样能够给读者削减许多担负,第三篇文章对同享办法进行了剖析。并且也不剖析 condition 部分,所以应该说很简略就能够看懂了。
  4. 本文许多运用咱们平常用得最多的 ReentrantLock 的概念,实质上来说是不正确的,读者应该清楚,AQS 不仅仅用来完结可重入锁,仅仅期望读者能够用锁来联想 AQS 的运用场景,下降阅览压力。
  5. ReentrantLock 的公正锁和非公正锁只要一点点差异,第二篇文章做了介绍。
  6. 谈论区有读者反应本文直接用代码说不友好,应该多配点流程图,这篇文章的确有这个问题。可是作为过来人,我想通知咱们,关于 AQS 来说,办法真的不重要,重要的是把细节说清楚。

AQS 结构

先来看看 AQS 有哪些特点,搞清楚这些基本就知道 AQS 是什么套路了,究竟能够猜嘛!

// 头结点,你直接把它作为 当时持有锁的线程 或许是最好了解的
private transient volatile符凡迪实在身份 Node head;
// 堵塞的尾节点,每个新的节点进来,都刺进到最终,也就形成了一个链表
private transient volatile Node tail;
// 这个是最重要的,代表当时锁的状况,0代表没有被占用,大于 0 代表有线程持有当时锁
// 这个值能够大于 1,是因为锁能够重入,每次重入都加上 1
private volatile int state;
// 代表当时持有独占锁的线程,举个最重要的运用比方,因为锁能够重入
// reentrantLock.lock()能够嵌套调用屡次,所以每次用这个来判别当时线程是否现已具有了锁
// if (currentThread == getExclusiveOwnerThread()) {state++}
private transient Thread exclusiveOwnerThread; //承继自AbstractOwnableSynchronizer

怎样样,看样子应该是很简略的吧,究竟也就四个特点啊。

AbstractQueuedSynchronizer 的等候行列暗示如下所示,留意了,之后剖析进程中所说的 queue,也便是堵塞行列不包括 head,不包括 head,不包括 head

等候行列中每个线程被包装成一个 Node 实例,数据结构是链表,一同看看源码吧:

static final class Node {
// 标识节点当时在同享办法下
static final Node SHARED = new Node();
// 标识节点当时在独占办法下
static final Node EXCLUSIVE = null;
// ======== 下面的几个int常量是给waitStatus用的 ===========
/** waitStatus value to indicate thread has cancelled */
// 代码此线程撤销了争抢这个锁
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
// 官方的描绘是,其表明当时node的后继节点对应的线程需求被唤醒
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
// 本文不剖析condition,所以略过吧,下一篇文章会介绍这个
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared 寻should
* unconditionally propagate
*/
// 相同的不剖析,略过吧
static final int PROPAGATE = -3;
// =====================================================
// 取值为上面的1、-1、-2、-3,或许0(今后会讲到)
// 这么了解,暂时只需求知道假如这个值 大于0 代表此线程撤销了等候,
// ps: 半响抢不到锁,不抢了,ReentrantLock是能够指定timeouot的。。。
volatile int waitStatus;
// 前驱节点的引证
volatile Node prev;
// 后继节点的引证
volatile Node next;
// 这个便是线程本尊
volatile Thread thread;
}

Node 的数据结构其实也挺简略的,便是 thread + waitStatus + pre + next 四个特点罢了,咱们先要有这个概念在心里。

上面的是根底知识,后边会多dnf补丁,吉他调音-战胜消费痛点,最具性价比的车型,你值得具有次用到,心里要时间记取它们,心里想着这个结构图就能够了。下面,咱们开端说 ReentrantLock 的公正锁。再次着重,我说的堵塞行列不包括 head 节点。

首要,咱们先看下 ReentrantLock 的运用办法。

// 我用个web开发中的service概念吧
public class OrderService {
// 运用stadnf补丁,吉他调音-战胜消费痛点,最具性价比的车型,你值得具有tic,这样每个线程拿到的是同一把锁,当然,spring mvc中service默许便是单例,别纠结这个
private static ReentrantLock reentrantLock = new ReentrantLock(true);
public void createOrder() {
// 比方咱们同一时间,只允许一个线程创立订单
reentrantLock.lock();
// 一般,lock 之后紧跟着 try 句子
try {
// 这块代码同一时间只能有一个线程进来(获取到锁的线程),
// 其他的线程在lock()办法上堵塞,等候获取到锁,再进来
// 履行代码...
// 履行代码...
// 履行代码...
} finally {
// 开释锁
reentrantLock.unlock();
}
}
}

ReentrantLock 在内部用了内部类 Sync 来办理锁,所以真实的获取锁和开释锁是由 Sync 的完结类来操控的。

abstract static class Sync extends AbstractQueuedSynchronizer {
}

Sync 有两个完结,分别为 NonfairSync(非公正锁)和 霍尼韦尔FairSync(公正锁),咱们看 FairSync 部分。

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

线程抢锁

许多人必定开端厌弃上面废话太多了,下面跟着代码走,我就不废话了。

static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// 争锁
final void lock() {
acquire(1);
}
// 来自父类AQS,我直接贴过来这边,下面剖析的时分相同会这样做,不会给读者带来阅览压力
// 咱们看到,这个办法,假如tryAcquire(arg) 回来true, 也就完毕了。
// 不然,acquireQueued办法会将线程压到行列中
public final void acquire(老公不卸职int arg) { // 此刻 arg == 1
// 首要调用tryAcquire(1)一下,姓名上就知道,这个仅仅试一试
// 因为有或许直接就成功了呢,也就不需求进行列排队了,
// 关于公正锁的语义便是:原本就没人持有锁,底子没必要进行列等候(又是挂起,又是等候被唤醒的)
if (!tryAcquire(arg) &&
// tryAcquire(arg)没有成功,这个时分需求把当时线程挂起,放到堵塞行列中。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
selfInterrupt();
}
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
// 测验直接获取锁,回来值是boolean,代表是否获取到锁
// 回来true:1.没有线程在等候锁;2.重入锁,线程原本就持有锁,也就能够天经地义能够直接获取
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// state == 0 此刻此刻没有线程持有锁
if (c == 0) {
// 尽管此刻此刻锁是能够用的,可是这是公正锁人皇,既然是公正,就得考究先来后到,
// 看看有没有他人在行列中等了半响了
if (!hasQueuedPredecessors() &&
// 假如没有线程在等候,那就用CAS测验一下,成功了就获取到锁了,
// 不成功的话,只能阐明一个问题,就在刚刚简直同一时间有个线程抢先了 =_=
// 因为刚刚还没人的,我判别过了
compareAndSetState(0, acquires)) {
// 到这儿便是获取到锁了,符号一下,通知咱们,现在是我占用了锁
setExclusiveOwnerThread(current);
return true;
}
}
// 会进入这个else if分支,阐明是重入了,需求操作:state=state+1
// 这儿不存在并发问题
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 假如到这儿,阐明前面的if和else if都没有回来true,阐明没有获取到锁
// 回到上面一个外层调用办法持续看:
// if (!tryAcquire(arg)
// && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// selfInterrupt();
return false;
}
// 假定tryAcquire(arg) 回来false,那么代码将履行:
// acquireQueued(addWaiter(Node.EXCLUSIVE), arg),
// 这个办法,首要需求履行:addWaiter(Node.EXCLUSIVE)
/**
* Creates and enqueues node for current thr习ead and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
// 此办法的效果是把线程包装成node,一起进入到行列中
// 参数mode此刻是Node.EXCLUSIVE,代表独占办法
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// 以下几行代码想把当时node加到链表的最终面去,也便是进到堵塞行列的最终
Node pred = tail;
// tail!=null => 行列不为空(tail==head的时分,其实行列是空的,不过不论这个吧)
if (pred != null) {
// 将当时的队尾节点,设置为自己的前驱
node.prev = pred;
// 用CAS把自己设置为队尾, 假如成功后,tail == node 了,这个节点成为堵塞行列新的尾巴
if (compareAndSetTail(pred, node)) {
// 进到这儿阐明设置成功,当时node==tail, 将自己与之前的队尾相连,
// 上面现已有 node.prev = pred,加上下面这句,也就完结了和之前的尾节点双向连接了
pred.next = node;
// 线程入队了,能够回来了
return node;
}
}
// 细心看看上面的代码,假如会到这儿,
// 阐明 pred==null(行列是空的) 或许 CAS失利(有线程在竞赛入队)
// 读者一定要跟上思路,假如没有跟上,主张先不要往下读了,往回细心看,不然会浪费时间的
enq(node);
return node;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
// 选用自旋的办法入队
// 之前说过,到这个办法只要两种或许:等候行列为空,或许有线程竞赛入队,
// 自旋在这边的语义是:CAS设置tail进程中,竞赛一次竞赛不到,我就屡次竞赛,总会排到的
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 之前说过,行列为空也会进来这儿
if (t == null) { // Must initialize
// 初始化head节点
// 细心的读者会知道原本 head 和 tail 初始化的时分都是 null 的
// 仍是一步CAS,你懂的,现在或许是许多线程一起进来呢
if (compareAndSetHead(new Node()))
// 给后边用:这个时分head节点的waitStatus==0, 看new Node()结构办法就知道了
// 这个时分有了head,可是tail仍是null,设置一下,
// 把tail指向head,定心,立刻就有线程要来了,到时分tail就要被抢了
// 留意:这儿仅仅设置了tail=head,这儿可没return哦,没有return,没有return
// 所以,设置完了今后,持续for循环,下次就到下面的else分支了
tail = head;
} else {
// 下面几行,和上一个办法 addWaiter 是相同的,
// 仅仅这个套在无限循环里,横竖便是将当时线程排到队尾,有线程竞赛的话排不上重复排
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
// 现在,又回到这段代码了
// if (!tryAcquire(arg)
// && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// selfInterrupt();
// 下面这个办法,参数node,通过addWaiter(Node.EXCLUSIVE),此刻现已进入堵塞行列
// 留意一下:假如acquireQueued(addWaiter(Node.EXCLUSIVE), arg))回来true的话,
// 意味着上面这段代码将进入selfInterrupt(),所以正常状况下,下面应该回来false
// 这个办法非常重要,应该说真实的线程挂起,然后被唤醒后去获取锁,都在这个办法里了
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// p == head 阐明当时节点尽管进到了堵塞行列,可是是堵塞行列的第一个,因为它的前驱是head
// 留意,堵塞行列不包括head节点,head一般指的是占有锁的线程,head后边的才称为堵塞行列
// 所以当时节点能够去试抢一下锁
// 这儿咱们说一下,为什么能够去试试:
// 首要,它是队头,这个是第一个条件,其次,当时的head有或许是刚刚初始化的node,
// enq(node) 办法里边有提到,head是延时初始化的,并且new Node()的时分没有设置任何线程
// 也便是说,当时的head不归于任何一个线程,所以作为队头,能够去试一试,
// tryAcquire现已剖析过了, 忘记了请往前看一下,便是简略用CAS试操作一下state
if (p == head && tryAcquire(arg)) {
setHea新加坡签证d(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 到这儿,阐明上面的if分支没有成功,要么当时node原本就不是队头,
// 要么便是tryAcquire(arg)没有抢赢他人,持续往下看
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 什么时分 failed 会为 true???
// tryAcquire() 办法抛反常的状况
if (failed)
cancelAcquire(node);
}
}
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
dnf补丁,吉他调音-战胜消费痛点,最具性价比的车型,你值得具有// 刚刚说过,会到这儿便是没有抢到锁呗,这个办法说的是:"当时线程没有抢到锁,是否需求挂起当时线程?"
// 第一个参数是前驱节点,第二个参数才是代表当时线程的节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 前驱节点的 waitStatus == -1 ,阐明前驱节点状况正常,当时线程需求挂起,直接能够回来true
dnf补丁,吉他调音-战胜消费痛点,最具性价比的车型,你值得具有if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
// 前驱节点 waitStatus大于0 ,之前说过,大于0 阐明前驱节点撤销了排队。
// 这儿需求知道这点:进入堵塞行列排队的线程会被挂起,而唤醒的操作是由前驱节点完结的。
// 所以下面这块代码说的是将当时节点的prev指向waitStatus<=0的节点,
// 简略说,便是为了找个好爹,因为你还得依靠它来唤醒呢,假如前驱节点撤销了排队,
// 找前驱节点的前驱节点做爹,往前遍历总能找到一个好爹的
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate r高粱米水饭etry.
*/
do {
node.prev = pred = pred.prev;
} while (悠悠pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
// 细心想想,假如进入到这个分支意味着什么
// 前驱节点的waitStatus不等于-1和1,那也便是只或许是0,-2,-3
// 在咱们前面的源码中,都没有看到有设置waitStatus的,所以每个新的node入队时,waitStatu都是0
// 正常状况下,前驱节点是之前的 tail,那么它的 waitStatus 应该是 0
// 用CAS将前驱节点的waitStatus设置为Node.SIGNAL(也便是-1)
compare昱怎样读AndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 这个办法回来 false,那么会再走一次 for 循序,
// 然后再次进来此办法,此刻会从第一个分支回来 true
return false;
}
// private static boolean shouldParkAfterFailedAcquire(Node pred, Node node)
// 这个办法完毕依据回来值咱们简略剖析下:
// 假如回来true, 阐明前驱节点的waitStatus==-1,是正常状况,那么当时线程需求被挂起,等候今后被唤醒
// 咱们也说过,今后是被前驱节点唤醒,就等着前驱节点拿到锁,然后开释锁的时分叫你好了
// 假如回来false, 阐明当时不需求被挂起,为什么呢?往后看
// 跳回到前面是这个办法
// if (shouldParkAfterFailedAcquire(p, node) &&
// parkAndCheckInterrupt())
// interrupted = true;
// 1. 假如shouldParkAfterFailedAcquire(p, node)回来true,
// 那么需求履行parkAndCheckInterrupt():
// 这个办法很简略,因为前面回来true,所以需求挂起线程,这个办法便是担任挂起线程的
// 这儿用了LockSupport.park(this)来挂起线程,然后就停在这儿了,等候被唤醒=======
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
// 2. 接下来说说假如shouldParkAfterFailedAcquire(p, node)回来false的状况
// 细心看shouldParkAfterFailedAcquire(p, node),咱们能够发现,其实第一次进来的时分,一般都不会回来true的,原因很简略,前驱节点的waitStatus=-1是依靠于后继节点设置的。也便是说,我都还没给前驱设置-1呢,怎样或许是true呢,可是要看到,这个办法是套在循环里的,所以第2次进来的时分状况便是-1了。
// 解说下为什么shouldParkAfterFailedAcquire(p, noddnf补丁,吉他调音-战胜消费痛点,最具性价比的车型,你值得具有e)回来false的时分不直接挂起线程:
// => 是为了应对在通过这个办法后,node现已是head的直接后继节点了。剩余的读者自己想想吧。
}

提到这儿,也就了解了,多看几遍 fin混沌天地诀al boolean杨辉直播间 acquireQueued(final Node node, int arg) 这个办法吧。自己推演下各个分支怎样走,哪种状况下会发作什么,走到哪里。

解锁操作

最终,便是还需求介绍下唤醒的动作了。咱们知道,正常状况下,假如线程没获取到锁,线程会被 LockSuppodnf补丁,吉他调音-战胜消费痛点,最具性价比的车型,你值得具有rt.park(this); 挂起中止,等候被唤醒。

// 唤醒的代码仍是比较简略的,你假如上面加锁的都看懂了,下面都不需求看就知道怎样回事了
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 往后看吧
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 回到ReentrantLock看tryRelease办法
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 是否彻底开释锁
boolean free = false;
// 其实便是重入的问题,假如c==0,也便是说没有嵌套锁了,能够开释了,不然还不能开释掉
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
// 唤醒后继节点
// 从上面调用途知道,参数node是head头结点
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possi张付川bly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
// 假如head节点当时waitStatus<0, 将其修改为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// 下面的代码便是唤醒后继节点,可是有或许后继节点撤销了等候(waitStatus==1)
// 从队尾往前找,找到waitStatus<=0的一切节点中排在最前面的
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从后往前找,细心看代码,不用忧虑中心有节点撤销(waitStatus==1)的鲁滨逊漂流记首要内容状况
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒线程
LockSupport.unpark(s.thread);
}

唤醒线程今后,被唤醒的线程将从以下代码中持续往前走:

private final boolean par格里兹曼kAndCheckInterrupt() {
LockSupport.park(this); // 刚刚线程被挂起在这儿了
return Thread.interrupted();
}
// 又回到这个办法了化妆品加盟店:acquireQueued(final Node node, int arg),这个时分,node的前驱是head了

好了,后边就不剖析源码了,剩余的还有问题自己去细心看看代码吧。

总结

总结一下吧。

在并发环境下,加锁和解锁需求以下三个部件的和谐:

  1. 锁状况。咱们要知道锁是不是被其他线程占有了,这个便是 state 的效果,它为 0 的时分代表没有线程占有锁,能够去争抢这个锁,用 CAS 将 state 设为 1,假如 CAS 成功,阐明抢到了锁,这样其他线程就抢不到了,假如锁重入的话,state进行 +1 就能够,解锁便是减 1,直到 st青海花儿擂台一切对唱ate 又变为 0,代表开释锁,所以 lock() 和 unlock() 必需要配对啊。然后唤醒等候行列中的第一个线程,让其来占有锁。
  2. 线程的堵塞和免除堵塞。AQS 中选用了 LockSupport.park(thread) 来挂起线程,用 unpark 来唤醒线程。
  3. 堵塞行列。因为争抢锁的线程或许许多,可是只能有一个线程拿到锁,其他的线程都必须等候,这个时分就需求一个 queue 来办理这些线程,AQS 用的是一个 FIFO 的行列,便是一个链表,每个 node 都持有后继节点的引证。AQS 选用了 CLH 锁的变体来完结,感兴趣的读者能够参阅这篇文章关于CLH的介绍,写得简略明了。

示例图解析

下面归于回忆环节,用简略的示例来说一遍,假如上面的有些东西没看懂,这儿还有一次协助你了解的时机。

首要,第一个线程调用 reentran龙青鲤tLock.lock(),翻到最前面能够发现,tryAcquire(1) 直接就回来 true 了,完毕。仅仅设置了 state=1,连 head 都没有初始化,更谈不上什么堵塞行列了。要是线程 1 调用 unlock() 了,才有线程 2 来,那国际就太太太平了,彻底没有交集嘛,那我还要 AQS 干嘛。

假如线程 1 没有调用 unlock() 之前,线程 2 调用了 lock(), 想想会发作什么?

线程 2 会初始化 head【new Node()】,一起线程 2 也会刺进到堵塞行列并挂起 (留意看这儿是一个 for 循环,并且设置 head 和 tail 的部分是不 return 的,只要入队成功才会跳出循环)

private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.nextdnf补丁,吉他调音-战胜消费痛点,最具性价比的车型,你值得具有 = node;
return t;
}
}
}
}

首要,是线程 2 初始化 head 节点,此刻 head==tail, waitStatus==0

然后线程 2 入队:

一起咱们也要看此刻节点的 waitStatus,咱们知道 head 节点是线程 2 初始化的,此刻的 waitStatus 没有设置, java 默许会设置为 0,可是到 shouldParkAfterFailedAcquire 这个办法的时分,线程 2 会把前驱节点,也便是 head 的waitStatus设置为 -1。

那线程 2 节点此刻的 waitStatus 是多少呢,因为没有设置,所以是 0;

假如线程 3 此刻再进来,直接插到线程 2 的后边就能够了,此刻线程 3 的 waitStatus 是 0,到 shouldParkAfterFailedAcquire 办法的时分把前驱节点线程 2 的 waitStatus 设置为 -1。

这儿能够简略说下 waitStatus 中 SIGNAL(-1) 状况的意思,Doug Lea 注释的是:代表后继节点需求被唤醒。也便是说这个 waitStatus 其实代表的不是自己的状况,而是后继节点的状况,咱们知道,每个 node 在入队的时分,都会把前驱节点的状况改为 SIGNAL,然后堵塞,等候被前驱唤醒。这儿触及的是两个问题:有线程撤销了排队、唤醒操作。其实实质是相同的,读者也能够顺着 “waitStatus代表后继节点的状爱的相对论态” 这种思路去看一遍源码。

the end
克服消费痛点,最具性价比的车型,你值得拥有