• AbstractQueuedSynchronizer(AQS)之 ReentrantLock 源码浅读


    了解 AbstractQueuedSynchronizer (AQS) 是什么?

    1.是用来构建锁或者其他同步器组件的重量级框架,是整个 JUC 底层实现,内置 FIFO队列完成资源获取和排队工作,并通过一个 int 变量来表示持有锁状态。

    2.是提出锁的一种规范,屏蔽同步状态的管理,阻塞线程排队和通知,唤醒机制。

    3.加锁就会阻塞线程,有阻塞就会产生排队,有排队就需要队列,既然说到排队,那么就一定需要有某种队列形成,这样的队列是什么数据结构呢?

    AQS 数据结构

    如果共享资源 state 被占用,就需要一定的阻塞唤醒机制来保证锁分配,这个机制主要用的是 CLH 队列变体实现的,将暂时获取不到锁的线程加入到 CLH队列 中,这个队列就是 AQS 的抽象表现,它将请求共享资源的线程封装到了队列的 Node 节点中,通过 CAS自旋以及 LockSupport.park() 方式,维护 AQS 中的变量 state 状态,使并发达到同步的效果。如下图示:
    在这里插入图片描述

    CLH(Craig,Landin and Hagersten)是三个人,共同发明了一个可扩展、高性能、公平且基于自旋锁的链表

    上述图中展示了 AQS 的框架结构,下面在继续查看该类内部相关信息。

    AQS 类结构及常用 API

    AbstractQueuedSynchronizer 是一个抽象类(既然是抽象类,肯定提供了模版方法要让我们自己实现的啦),先来看看有哪些成员(摘出的是 AQS 主流程需要用到的成员信息),如下所示:

    public abstract class AbstractQueuedSynchronizer {
    	
    	// AQS 同步器中指向头结点的引用
        private transient volatile Node head;
    	// AQS 同步器中指向尾节点的引用
        private transient volatile Node tail;
    	// 线程共享资源
        private volatile int state;
        // Node 内部类封装Thread和线程状态,以及锁是独占锁还是共享锁
        static final class Node {
            // 共享锁
            static final Node SHARED = new Node();
            // 独占锁(默认)
            static final Node EXCLUSIVE = null;
            // 线程结束运行了
            static final int CANCELLED =  1;
            // 表示线程已经被阻塞了需要等待被唤醒
            static final int SIGNAL    = -1;
            // 共享锁涉及
            static final int CONDITION = -2;
            static final int PROPAGATE = -3;
    		// 线程的状态,初始值是 0 
            volatile int waitStatus;
        	// Node 的前序节点
            volatile Node prev;
        	// Node 的后驱结点
            volatile Node next;
    		// 当前线程
            volatile Thread thread;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    AQS 类组成结构:

    • 从上述代码中可以看到有四个非常重要的属性(headtailstate内部类Node),AQS 类设计理念是队列,遵循 FIFO 原则,所以自然而然肯定有 headtail 两个头尾节点,然后共享资源 state ,还有个 Node 内部类组成 AQS 抽象类。
    • 父类 AbstractOwnableSynchronizer 类中还有一个属性 exclusiveOwnerThread,用来存储加锁成功的线程引用。

    Node 内部类组成结构:

    • Node 里封装的是 Thread 线程
    • waitStatus 表示线程的状态(默认初始值是0、还有 CANCELLED(1)SIGNAL(-1)CONDITION(-2)、 - PROPAGATE(-3) 总共五种状态)
    • prev 前驱节点
    • next 后驱节点
    • mode 表示是要加独占锁还是共享锁(默认独占锁 EXCLUSIVE)

    由此可以看出 AQS 是由上述两大部分组成,里面的内部类 Node 其实是一个双端链表。

    因为 AQS 是个抽象类,必不可少的肯定要儿子类去帮他完成一些事情,所以除了上面的属性之外还提供了4个常用 API 如下:

    • tryAcquire(int arg)   // 尝试加独占锁
    • tryAcquireShared(int arg)   // 尝试加共享锁
    • tryRelease(int arg)   // 尝试释放独占锁
    • tryReleaseShared(int arg)   // 尝试释放共享锁

    总结起来:同步器中有一个 state 变量控制锁的装态,同时还要知道哪个线程作为 headtail节点,外层就只有这三个重要结构,内部有个 Node 类封装了线程以及状态,Node 结构还是一个双向链表,prenext节点的指向前后的 Node 节点。

    总结流程图如下:
    在这里插入图片描述

    AQS 源码分析

    AQS 本身是一个抽象类,里面的方法都是空方法,所以我们从它的著名子类 ReentrantLock 开始分析,先来看一个最简单熟悉不过的代码,如下:

    public class LockSupportDemo {
    
        public static final Lock lock = new ReentrantLock();
    
        public static void main(String[] args) {
            lock.lock();
            // ...doSomething
            lock.unlock();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    进入 lock 内部方法,如下:

        public void lock() {
            sync.lock();
        }
    
    • 1
    • 2
    • 3

    发现调用的是 sync.lock(),那这个 sync 是啥玩意呢?继续查看 sync 代码如下:

    public class ReentrantLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = 7373984872572414699L;
        
        private final Sync sync;
    
        abstract static class Sync extends AbstractQueuedSynchronizer {
            private static final long serialVersionUID = -5179523762034025860L;
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    发现 ReentrantLock 底层实际用的是 AQS,自己写了个内部类叫 Sync,继承 AQS,覆写 AQS 中提供的API :

    • tryAcquire(int arg)   // 尝试加独占锁
    • tryAcquireShared(int arg)   // 尝试加共享锁
    • tryRelease(int arg)   // 尝试释放独占锁
    • tryReleaseShared(int arg)   // 尝试释放共享锁

    继续进入 sync.lock() 内部发现他有两个子类实现,如下:
    在这里插入图片描述
    发现是我们平常所说的公平锁,和非公平锁,我们这里先看非公平锁 NonfairSync,代码如下:

        static final class NonfairSync extends Sync {
            private static final long serialVersionUID = 7316153563782823691L;
    
            // 调用的子类 NonfairSync 里的 lock 方法
            final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
    
            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    发现 NonfairSync 又是 Sync 的子类,调用 lock 方法,内部可以看到是使用 CPU 原语指令 CAS 操作。进来就开始尝试 CAS 加锁,加锁成功返回,失败走 acquire(1),因为第一次进来,state 默认值是 0,执行这条指令 compareAndSetState(0, 1) 肯定是成功的,然后加锁成功的线程会把自己的大名贴上,我占用了这把锁,我没有释放之前,其他线程是不能加锁的(独占锁)。

    但是如果此时其他线程过来抢夺这把锁,也就是想修改 state 的值,假设线程1没有释放锁,那就只能走 else 操作了,进入 acquire(1) 内部,代码如下:

    public abstract class AbstractQueuedSynchronizer
        extends AbstractOwnableSynchronizer
        implements java.io.Serializable { 
        
        public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    会发现有三部分构成tryAcquire()acquireQueued()addWaiter()。这里开始调用 AQS 提供的模版方法,tryAccquire() 这个方法 AQS 是没有实现的,子类 ReentrantLock 自己实现,后面的 addWaiter() acquireQueued() 方法都是 AQS 内置方法,不需要程序员去管理的。先进入到 tryAccquire() 方法内部,如下:

     abstract static class Sync extends AbstractQueuedSynchronizer {
    
            final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    if (compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    线程过来会先通过调用 AQS 提供的 getState() 方法取出内存中的 state 值,判断是否为0 ,0是没有人修改过,非0肯定是有其他线程修改过,很显然,线程1没有释放锁,现在这个线程肯定是不能加锁成功的。else if 中的逻辑是判断可重入锁的逻辑,如果判断当前线程和 AQS 中贴上的线程是一致的,那就给 state 加 1,释放的时候也要减1,直到减到1才算释放成功。显然当前线程肯定不是 AQS 上加锁成功的线程,所以当前线程加锁失败,返回 false,然后再返回上一层逻辑。

    继续执行后面的语句 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 这条语句我们先看最里面的 Node.EXCLUSIV 表示要创建一把独占锁,但是此时这个默认值是 null,也就是说现在传入的 Node = null,代码如下:
    在这里插入图片描述

    然后再进入到 addWaiter() 方法内部,如下:

    abstract static class Sync extends AbstractQueuedSynchronizer {
    
        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 pred = tail;
            if (pred != null) {
                node.prev = pred;
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            enq(node);
            return node;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    会发现,这里新建了一个 node 节点(暂时把名字叫做 Node1),并把当前线程封装进去了,同时设置为独占锁模式。然后 tail 是 AQS 中的属性,用来指向队列的尾节点,默认值是 null,所以这里 pred = null,直接走 enq(node) 代码,进入方法内部,如下:

        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.next = node;
                        return t;
                    }
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    发现是一个自旋 CAS 操作,线程进入到 for 循环,因为第一次进来 tail = null ,所以会新建一个空 Node 节点(暂时把名字叫做 Node2),里面的属性(prev,next,thread,waitStatus(默认值0))全部都还没有赋值,然后执行代码 compareAndSetHead(new Node()) ,表示将 AQS 同步器中的 head 头指针指向新建的空节点(也可称之为哨兵节点),此时 Thread = nullwaitStatus=0 ,如下图示:
    在这里插入图片描述

    1.如果没有哨兵节点,那么每次执行入队操作,都要判断 head 是否为空,如果为空则 head=new Node 如果不为空则 head.next=new Node, 而有哨兵节点则可以直接建立引用 head.next=new Node
     
    2.如果没有哨兵节点,可能存安全性问题,当只有一个节点的时候执行入队方法,无法保证 tailhead 不为空。哪怕节点入队前 tailhead 还指向同一个节点,下一时刻可能由于并发性在具体调用 enqueue 方法操作 tail 的时候, headtail 共同指向的头节点已经完成出队,此时 tailhead 都为 null,所以enqueue方法中的tail.next=new node 会抛空指针异常,这就是哨兵节点添加进去的可以有效避免的问题。

    Node1 是线程过来新建的不为空的节点,里面的封装着 ThreadB,waitStatus=0;Node2 是线程过来新建的空节点,Thread=null, waitStatus=0。然后再执行代码 tail = head,表示将 AQS 中尾指针指向哨兵节点,如下图示:
    在这里插入图片描述
    注意,循环还没有结束呢,然后再走一遍逻辑,此时的 tail 肯定是不为 null 的,已经指向了 Node2 哨兵节点,然后执行代码 node.prev = t;(t是tail,指向了 Node2 哨兵节点,node 就是我们传进来的 Node1) 这句代码表示将Node1 的前驱指针指向 tailtail 此时指向的是 Node2 哨兵节点,如下图示:
    在这里插入图片描述
    然后执行代码 compareAndSetTail(t, node) 表示把 AQS 的尾指针指向 Node1,队列尾指针肯定是永远指向尾部的。如下图示:
    在这里插入图片描述
    然后再执行代码 t.next = node; (t是tail,原来指向的是 Node2 哨兵节点) 这句代码表示将 Node2 哨兵节点的后驱指针指向 Node1 节点,如下图示:
    在这里插入图片描述
    程序运行到此整个 enq() 方法就算结束了,后面就不会再进来了,返回上一层,代码如下:

        private Node addWaiter(Node mode) {
            Node node = new Node(Thread.currentThread(), mode);
            
            Node pred = tail;
            if (pred != null) {
                node.prev = pred;
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            enq(node);
            return node;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    看到线程执行完 enq() 方法之后,就把当前的 node(这里我们上面说了暂时命名为 Node1 节点) 返回出去,我们这里先不返回出去,先来看看这个 if 里面的逻辑,假设其他线程第三次过来执行这个 addWatier () 方法,此时传过来的 node 我们称之为 Node3,此时 tail 肯定不为 null(已经指向了 Node1 节点),所以会进入 if 的逻辑,开始执行 这行代码 node.prev = pred; 表示把 Node3 节点的前驱指针指向 predpred是谁呢?是tail,测试 tail 指向的是尾部节点 Node1,上述过程演示过的,尾部节点是 Node1,所以自然而然,这里把 Node3 的前驱指针指向了 Node1,如下图示:
    在这里插入图片描述
    然后再执行代码 compareAndSetTail(pred, node) 把 AQS 的 tail 尾部尾指针向后移动,指向最后一个节点,也就是最后入队列的节点 Node3 ,如下图示:
    在这里插入图片描述
    执行完之后,在执行代码 pred.next = node;pred 原来的尾指针指向的对象,也就是 Node1 节点) 意思是把 Node1 节点的后驱指针指向 Node3,如图示:
    在这里插入图片描述
    最后返回 Node3 节点,然后这整个 addWaiter() 方法就分析完了,无非就是在两件事情,第一是创建空的哨兵节点,第二将所有的过来的 Thread 入队然后建立好引用,分析完 addWaiter() 然后返回代码上一层,代码如下:

        public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    继续进入 acquireQueued() 方法内部,代码如下:

        final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return interrupted;
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    第一个线程过来执行代码 node.predecessor() 表示返回节点的前驱节点。

    假设1我们现在过来的节点是 Node1 节点,arg 是外面写死的1,那么 Node1 的前驱节点自然而然是 Node2 哨兵节点(由上面最后一张图可以看出)

    然后执行在执行 if 里面的判断逻辑,发现 p 指向的是 Node2head 指向的也是 Node2,所以 p==head 条件成立,表示你这个节点是即将第一个出队列的节点,所以紧接着就会让你重新尝试一次加锁,也就是执行代码tryAcquire(arg),也就是走下面这段代码:

            final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    if (compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    假设1的第1种情况如果过来的线程加锁成功,那么就会进入到 if 里面的逻辑,执行代码 setHead(node); 先看看 setHead() 方法内部,代码如下:

        private void setHead(Node node) {
            head = node;
            node.thread = null;
            node.prev = null;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    表示把 AQS 的 head 头指针指向了 Node1 节点,其实就是相当于把 Node1 节点出队列了,然后 Node1 节点会被当做新哨兵节点,如下图示:
    在这里插入图片描述

    虚线标识是没有指向了,灰色表示没有任何引用了,可以被 GC 垃圾回收,此时 AQS 的头指针指向了 Node1 节点,Node1 节点会被当做新的哨兵节点,那么此时里面的 Thread 也可以置为空,因为 Node1 节点中的 ThreadB 已经加锁成功了,已经出队列了,然后 Node3 节点也是和 Node1 节点一样加锁成功就出队列。

    假设1的第二种情况如果加锁失败了,就会执行 shouldParkAfterFailedAcquire(p, node) 方法,进入代码如下:

        private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
            int ws = pred.waitStatus;
            if (ws == Node.SIGNAL)
                return true;
            if (ws > 0) {
                do {
                    node.prev = pred = pred.prev;
                } while (pred.waitStatus > 0);
                pred.next = node;
            } else {
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
            }
            return false;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Node1 节点过来执行,pred 节点是哨兵节点,此时的 waitStatus=0,没人修改过,会走到最后一个else 执行代码 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 表示将哨兵节点的 waitStatus 状态改成 SIGNAL(-1)状态,其实这个方法就是每次新过来的节点会把前置节点的 waitStatus 修改成Node.SIGNAL(-1)状态, 然后回到外层,是一个 for 循环,还会再次进入在此进行判断一遍,此时哨兵节点中 waitStatus 已经被赋值为 -1,所以会返回 true,回到上一层代码如下:

        final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return interrupted;
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    如下图示:
    在这里插入图片描述
    然后再进入到方法 parkAndCheckInterrupt() 内部,代码如下:

        private final boolean parkAndCheckInterrupt() {
            LockSupport.park(this);
            return Thread.interrupted();
        }
    
    • 1
    • 2
    • 3
    • 4

    让当前线程挂起,如果没有中断 Thread.interrupted() 返回 false,并复位也还是 false

    那么这个挂起的线程什么时候被唤醒呢?为什么会被挂起呢?肯定是因为想去尝试加锁,加锁失败,那么只有等锁要被释放的时候,这些阻塞的线程才会被唤醒然后去竞争加锁,所以这里就需要看释放锁的过程了,还是比较简单的。

    进入代码 lock.unlock() 内部,代码如下:

        public void unlock() {
            sync.release(1);
        }
    
    • 1
    • 2
    • 3

    注意这里是传入的常量值1哦,再进入 release() 方法内部,代码如下:

        public final boolean release(int arg) {
            if (tryRelease(arg)) {
                Node h = head;
                if (h != null && h.waitStatus != 0)
                    unparkSuccessor(h);
                return true;
            }
            return false;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    进入到 tryRelease() 方法内部,代码如下:

            protected final boolean tryRelease(int releases) {
                int c = getState() - releases;
                if (Thread.currentThread() != getExclusiveOwnerThread())
                    throw new IllegalMonitorStateException();
                boolean free = false;
                if (c == 0) {
                    free = true;
                    setExclusiveOwnerThread(null);
                }
                setState(c);
                return free;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    会发现释放锁比较简单,也就是将之前的修改的 state 开始复位操作,然后把占用的坑位清空即可。假设释放锁成功了,返回true,开始执行代码 unparkSuccessor(),进入内部代码如下:

        private void unparkSuccessor(Node node) {
            int ws = node.waitStatus;
            if (ws < 0)
                compareAndSetWaitStatus(node, ws, 0);
    
            Node s = node.next;
            if (s == null || s.waitStatus > 0) {
                s = null;
                for (Node t = tail; t != null && t != node; t = t.prev)
                    if (t.waitStatus <= 0)
                        s = t;
            }
            if (s != null)
                LockSupport.unpark(s.thread);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Node 是传进来的 head 头指针指向的节点,也就是 Node2 哨兵节点,此时 Node2waitStatus 已经在加锁过程中的 shouldParkAfterFailedAcquire() 方法修改成了 Node.SIGNAL 状态(-1),所以此时第一个 if 逻辑是会执行的,执行 compareAndSetWaitStatus(node, ws, 0); 表示将 Node1 哨兵节点的 waitStatus 重新改为默认值 0 ,此时的 Node2 的后驱节点是 Node3,所以 s!=null 条件成立,开始执行 LockSupport.unpark(s.thread); 唤醒操作。

    那么唤醒之后,原来阻塞在那边的线程就要开始复活,然后接着从阻塞线程的地方开始往下分析,代码如下;

        final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return interrupted;
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    parkAndCheckInterrupt() 方法刚才挂起了线程,现在 Node1 线程被唤醒,那么会立即执行么?不一定的哦,因为可能有新建来的线程竞争插队执行的,假设没有线程插队,Node1 唤醒,然后开始往下执行继续走 for 循环,又开始执行第一个if判断,p==head 成立,尝试加锁也成功,然后把 Node1 作为哨兵节点,并隔离和原来哨兵节点的引用。

    假设现在有阻塞的线程被中断了,那么在执行这句代码 parkAndCheckInterrupt() 会返回当前的中断标志位,并且还复位了,代码如下:

        private final boolean parkAndCheckInterrupt() {
            LockSupport.park(this);
            return Thread.interrupted();
        }
    
    • 1
    • 2
    • 3
    • 4

    被中断了那 Thread.interrupted() 返回结果 true,但是这个方法会复位(线程自己操作的,不是你干的,你是要中断,他又帮你复位了,所以他需要再别地方在重新帮你中断一次,不然你的中断就无任何意义了),在外层还会自己调用一次中断,重新中断下。代码如下:

        public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
        static void selfInterrupt() {
            Thread.currentThread().interrupt();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里重新帮你执行一次interrupt() 方法再次中断标识位。但是只是设置了中断标志位,如果你自己灭有在某个地方退出线程,此线程还是可以加锁成功的。也就是你设置了跟没设置一个样,毫无影响。

    方法之间的调用关系

    在这里插入图片描述

  • 相关阅读:
    day14网络编程
    ChatGPT调教指南 | 咒语指南 | Prompts提示词教程(三)
    Qt/C++音视频开发53-本地摄像头推流/桌面推流/文件推流/监控推流等
    Python自动化必不可少的测试框架 — pytest
    BERT和ViT简介
    HashMap(2)正文源码分析
    高并发下如何防重?
    Nginx编译安装,信号,升级nginx
    Vue3 源码阅读(4):响应式系统 —— watch、computed
    【无标题】
  • 原文地址:https://blog.csdn.net/qq_35971258/article/details/125875213