• 提升--09-1--AQS底层逻辑实现


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


    一、怎么解释AQS是什么?

    AQS的本质是JUC包下一个抽象类,AbstractQueuedSynchronizer (抽象的队列式同步器)

    • AQS是JUC下的一个基础类,目的是为了提供一个共性的功能,让其他类去继承,比如ReentrantLock,CountDownLatch,Semaphore,ThreadPoolExecutor…………
      在这里插入图片描述在这里插入图片描述

    二、AQS核心底层和Lock是什么关系?

    ReentrantLock的互斥锁功能就是基于AQS实现的。

    通过源码可以看到,ReentrantLock类并没有直接继承AQS,而是ReentrantLock的内部类Sync继承了AQS。

    在这里插入图片描述

    Sync也是一个抽象类,他下面有两个实现。一个FairSync,一个NonfairSync。一个公平锁,一个非公平锁。

    在这里插入图片描述

    在这里插入图片描述
    在ReentrantLock中,公平锁和非公平锁对于lock方法和tryAcquire方法的实现是不同的。

    优先聊一下lock方法的区别。

    // 非公平锁的lock方法
    final void lock() {
        // 不管是否有线程在持有锁资源,直接尝试将state从0改为1,尝试拿锁。
        if (compareAndSetState(0, 1))
            // 拿锁成功了。将当前线程设置到exclusiveOwnerThread
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 前面抢锁失败走acquire
            acquire(1);
    }
    
    // 公平锁的lock方法
    final void lock() {
        acquire(1);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    分析一下acquire方法中做了什么事

    // acquire方法实现
    public final void acquire(int arg) {
        //1、tryAcquire方法:尝试获取锁资源的过程。拿到锁返回true,反之返回false。
        // 没拿到锁,才会走2和3。
        //2、addWaiter方法: 将当前没拿到锁的线程封装为Node,添加到同步队列。
        //3、acquireQueued方法: 长时间等待需要挂起线程,并且等到线程排到第一名时,
        //                      需要再次尝试获取锁资源
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    三、AQS如何尝试获取资源?

    tryAcquire方法是如何尝试获取锁资源的,tryAcquire方法有两种实现,一种是公平,一种是非公平。

    • 判断state为0,那就根据情况抢锁
    • state不为0,但是当前线程持有锁,那就走锁重入的逻辑
    • 前面都不满足,告辞!

    非公平锁的tryAcquire实现

    // 非公平锁的实现。
    final boolean nonfairTryAcquire(int acquires) {
        // 拿到当前线程。
        final Thread current = Thread.currentThread();
        // 获取state
        int c = getState();
        // 判断state是否为0
        if (c == 0) {
            // 当前没有线程持有锁。非公平锁直接尝试抢锁,抢成功就返回true
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 有线程持有锁。那就判断持有锁的线程是不是当前线程。
        else if (current == getExclusiveOwnerThread()) {
            // 到这说明是锁重入操作。
            // 将state + 1
            int nextc = c + acquires;
            // 判断+1之后,如果小于0,说明超过int正整数的取值范围了,无法再次重入~
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 将 + 1后的值赋值给state
            setState(nextc);
            // 返回true,锁重入成功~
            return true;
        }
        //没拿到锁,返回false
        return false;
    }
    
    
    • 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

    公平锁的实现

    // 公平锁的实现。
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 公平锁这里多了一行hasQueuedPredecessors()
            // 如果没有线程持有锁资源,优先查看是否有排队的线程
            // 1、如果没有线程排队,返回false,代表可以抢锁。
            // 2、如果有排队的,但是当前线程排在“第一名”,返回false,代表可以抢锁。
            // 3、如果有排队的,但是当前线程没有排在“第一名”,返回true,代表不可以抢锁。
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;a
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                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
    • 22
    • 23
    • 24
    • 25
    • 26

    四、AQS获取资源失败如何排队?

    如果获取锁资源失败,会执行addWaiter方法,去做排队操作。

    1. 将当前线程封装Node。
    2. 如果同步队列为null,需要优先初始化一个虚拟的Node节点
    3. 将当前Node添加到tail的后面。
    private Node addWaiter(Node mode) {
        // 将当前线程封装Node
        Node node = new Node(Thread.currentThread(), mode);
        // 拿到尾结点
        Node pred = tail;
        // 不为null,代表现在有Node对象。
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 如果pred是null,
        enq(node);
        return node;
    }
    // 将node添加到同步队列
    private Node enq(final Node node) {
        // 死循环是为了确保一定能添加成功
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                // 初始化一个没有线程信息的Node,作为头尾
                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
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    五、AQS排队后如何重新尝试获取资源?

    • 挂起需要确保prev节点的状态为-1。
    • 重新获取锁资源需要确保当前node是head.next,并且基于tryAcquire去获取锁资源。
    // 排队后的挂起操作和获取锁资源的操作
    // node就是刚刚去排队的Node
    final boolean acquireQueued(final Node node, int arg) {
        // 拿锁失败了么??true
        boolean failed = true;
        try {
            // 死循环,拿到锁才能走!!
            for (;;) {
                // 拿到当前Node的上一个Node
                final Node p = node.prev;
                // 只有head.next的node才有资格抢锁
                if (p == head && tryAcquire(arg)) {
                    // 说明拿到锁资源了。
                    // 当前node成为新的head,线程和prev都设置为null
                    setHead(node);
                    p.next = null; 
                    failed = false;
                    // 拿到成功,返回中断标记位(这里省略了这部分代码)
                    return false;
                }
                // 没资格拿,或者没拿到!
                // 需要优先掌握一个知识,Node中有一个waitStatus的状态
                // 1:代表当前Node取消了,不排了,告辞,走人。
                // 0:代表默认状态,啥事没有~
                // -1:代表当前Node的next节点可能挂起了。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    // 基于Unsafe类的park方法,将当前线程挂起!
                    parkAndCheckInterrupt())
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // pred是prev
        // node是curr
        // 拿到上一个Node的状态
        int ws = pred.waitStatus;
        // 如果上一个Node状态是-1,返回true,代表可以挂起
        if (ws == Node.SIGNAL)
            return true;
    
        // 上一个节点状态是否是取消状态
        if (ws == 1) {
            // 绕过状态为1的节点,找到一个状态正常的。
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus == 1);
            pred.next = node;
        } else {
            // 如果上一个节点状态正常,直接修改为-1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    六、AQS如何释放资源?

    • 加锁是执行lock,释放锁资源玩的是unlock方法。
    • 释放锁会对state做-1操作,如果-1后state为0,代表释放锁资源成功。
    • 如果锁资源释放成功,需要查看head状态是否为-1,如果为-1需要唤醒离head最近的有效节点。
    // 释放锁资源
    public final boolean release(int arg) {
        // 执行tryAcquire释放锁资源
        if (tryRelease(arg)) {
            // 释放干净了!
            // 先拿head
            Node h = head;
            // 如果head不为null,head的状态是否为 -1
            if (h != null && h.waitStatus == -1)
                // 唤醒后面挂起的线程(睡觉的线程)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    // 释放锁操作。
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        // 释放锁资源的线程必须是持有锁资源的线程,否则甩你一个异常。
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        // free代表锁释放干净了么?
        boolean free = false;
        // 如果state为0,代表锁资源释放干净了
        // 没进if,就代表没释放干净
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
    
        setState(c);
        return free;
    }
    
    // 唤醒后续挂起的线程   node是head
    private void unparkSuccessor(Node node) {
        // 拿到head的状态
        int ws = node.waitStatus;
        // 状态是-1,归位0
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        // s可能就是要被唤醒的线程
        Node s = node.next;
        // 如果s节点出现了问题,不排队了,那就找离head最近的有效节点唤醒!
        if (s == null || s.waitStatus > 0) {
            s = null;
            // 会从tail开始往前找,找到离head最近的有效节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        // 如果s不为null,代表找到了具体要唤醒的Node
        if (s != null)
            // 唤醒对应的线程
            LockSupport.upnark(s.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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
  • 相关阅读:
    工学云打卡签到自动实现关于异地时定位的问题解决|蘑菇钉
    下班前几分钟,逮到一个腾讯10年老测试开发,聊过之后彻底悟了...
    Linux的命令行
    echarts中,X轴名称过长隐藏,鼠标hove显示全称
    Spring源码:Bean生命周期(五)
    Python自学笔记
    编译和链接到底是什么
    最完整的Windows系统安装教程(Win7、Win10、Win11)
    102. 二叉树的层序遍历
    MySQL 索引
  • 原文地址:https://blog.csdn.net/weixin_48052161/article/details/134539171