• AQS解读


    1. AQS是什么

    state:有一个int类变量表示持有锁的状态,通过CAS完成对state值的修改(0表示没有,1表示阻塞,大于1表示重入锁)
    CLH:通过内置的CLH(FIFO)队列的变种来完成资源获取线程的排队工作,将每条将要去抢占资源的线程封装成一个Node节点来实现锁的分配

    如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。它将请求共享资源的线程封装成队列的结点(Node) ,通过CAS自旋以及LockSuport.park()的方式,维护state变量的状态,使并发达到同步的效果

    2. AQS内部结构

    在这里插入图片描述

    3.1 队列

    在这里插入图片描述

    3.2 队列节点Node

    //此处是 Node 的部分属性
    static final class Node {
     
     //排他锁标识
     static final Node EXCLUSIVE = null;
     //如果带有这个标识,证明是失效了
     static final int CANCELLED =  1;
     //具有这个标识,说明后继节点需要被唤醒
     static final int SIGNAL = -1;
    
     //Node对象存储标识的地方
     volatile int waitStatus;
    
     //指向上一个节点
     volatile Node prev;
    
     //指向下一个节点
     volatile Node next;
     
     //当前Node绑定的线程
     volatile Thread thread;
     
     //返回前驱节点即上一个节点,如果前驱节点为空,抛出异常
     final Node predecessor() throws NullPointerException {
      Node p = prev;
      if (p == null)
       throw new NullPointerException();
      else
       return p;
     }
    }
    
    • 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

    3.3 Node节点属性waitstatus

    CANCELLED(1):当前节点取消获取锁。当等待超时或被中断(响应中断),会触发变更为此状态,进入该状态后节点状态不再变化;
    SIGNAL(-1):后面节点等待当前节点唤醒;
    CONDITION(-2):Condition中使用,当前线程阻塞在Condition,如果其他线程调用了Condition的signal方法,这个结点将从等待队列转移到同步队列队尾,等待获取同步锁;
    PROPAGATE(-3):共享模式,前置节点唤醒后面节点后,唤醒操作无条件传播下去;
    0:中间状态,当前节点后面的节点已经唤醒,但是当前节点线程还没有执行完成;

    3. ReentrantLock开始解读AQS

    public class AQSDemo {
        public static void main(String[] args) {
            ReentrantLock lock = new ReentrantLock();
            //带入一个银行办理业务的案例来模拟我们的AQS如何进行线程的管理和通知唤醒机制
            //3个线程模拟3个来银行网点,受理窗口办理业务的顾客
            //A顾客就是第一个顾客,此时受理窗口没有任何人,A可以直接去办理
            new Thread(() -> {
                    lock.lock();
                    try{
                        System.out.println("-----A thread come in");
    
                        try { TimeUnit.MINUTES.sleep(20); }catch (Exception e) {e.printStackTrace();}
                    }finally {
                        lock.unlock();
                    }
            },"A").start();
    
            //第二个顾客,第二个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时B只能等待,
            //进入候客区
            new Thread(() -> {
                lock.lock();
                try{
                    System.out.println("-----B thread come in");
                }finally {
                    lock.unlock();
                }
            },"B").start();
    
            //第三个顾客,第三个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时C只能等待,
            //进入候客区
            new Thread(() -> {
                lock.lock();
                try{
                    System.out.println("-----C thread come in");
                }finally {
                    lock.unlock();
                }
            },"C").start();
        }
    }
    
    • 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

    在这里插入图片描述

    3.1 线程A执行过程

    在这里插入图片描述
    在这里插入图片描述
    线程A执行的上面截图圈中的代码,因为这时候state为0,所以线程A可以通过CAS自旋将state改成1,并把自己线程ID保存进去

    3.2 线程B执行过程

    在这里插入图片描述
    因为线程A已经拿到了资源,所以线程B只能走else逻辑,一共三个核心方法
    tryAcquire(arg)
    addWaiter(Node.EXCLUSIVE), arg)
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

    3.2.1 tryAcquire(arg)

    在这里插入图片描述

    1. 再次判断state状态是否0,是的话获取锁资源(状态位改为1,将自己线程ID保存进去),返回ture
    2. 判断当前线程是否已经获取锁的线程,是的话state+1,返回ture
    3. 如果以上标间都不满足,返回false

    3.2.2 addWaiter(Node.EXCLUSIVE), arg)

    在这里插入图片描述
    因为这时候队列还没有数据,尾节点是null,执行enq(node)

    3.3.2.1 enq(node)

    在这里插入图片描述
    第一次自旋:
    尾节点为空,创建一个空节点,并作为头节点,尾节点也指向这个空节点
    在这里插入图片描述
    第二次自旋:
    将当前节点加入队列,尾节点指向当前节点(挂在哨兵节点后)
    在这里插入图片描述

    3.2.3 acquireQueued(final Node node, int arg)

    第一次自旋:
    在这里插入图片描述
    在这里插入图片描述
    第二次自旋
    在这里插入图片描述

    3.3 线程C执行结果

    执行结果如图,最终都调用到LockSupport.park(当前线程),均阻塞
    在这里插入图片描述

    3.4 unlock执行过程

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    unpark(头节点)

    在这里插入图片描述

    4. 公平锁和非公平锁的区别

    在这里插入图片描述
    公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的, 都要进入到阻塞队列等待唤醒。

    相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

    4.1 代码

    非公平锁

     final void lock() {
     //直接尝试获取锁
     if (compareAndSetState(0, 1))
     setExclusiveOwnerThread(Thread.currentThread());
     else
     acquire(1);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    公平锁

     final void lock() {
     acquire(1);
     }
    
    • 1
    • 2
    • 3

    还要一个重要的区别是在尝试获取锁时tryAcquire(arg),公平锁hasQueuedPredecessors()判断队列中是否还有其他线程:

     protected final boolean tryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();
     if (c == 0) {
     if (!hasQueuedPredecessors() &&
     compareAndSetState(0, acquires)) {
     setExclusiveOwnerThread(current);
     return true;
     }
     }
     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

    视频讲解:https://www.bilibili.com/video/BV1uX4y1u7ht?p=26&vd_source=b901ef0e9ed712b24882863596eab0ca
    公平锁和非公平锁的区别:https://zhuanlan.zhihu.com/p/94015814

  • 相关阅读:
    Attingo:西部数据部分SSD存在硬件设计制造缺陷
    记一次线上bug排查-----SpringCloud Gateway组件 请求头accept-encoding导致响应结果乱码
    有哪些前端可以做的性能优化点
    RPC协议
    【Linux】进程控制基础知识
    基于SSM的网络安全宣传网站设计与实现
    [plugin:vite:css] [sass] Undefined mixin.
    流程控制.
    箭头函数写法
    论有一个面板小程序物料库的重要性!开发效率提升两三倍
  • 原文地址:https://blog.csdn.net/yzx3105/article/details/127676937