Qouson's blog Qouson's blog
首页
  • Java 基础

    • 基础
    • String
  • Java 中级

    • 网络编程
  • Java 高级

    • JVM
    • 多线程
  • Spring
  • SpringMVC
  • SpringBoot
  • MySQL
  • Redis
  • MQ
  • ZooKeeper
  • git
  • linux
  • 设计模式
  • 数据结构与算法
  • 计算机基础
  • Java相关框架
  • 分布式
  • DDD领域驱动设计
  • 系统设计
  • 杂乱无章
Java知识图谱
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

qouson

Java界的小学生
首页
  • Java 基础

    • 基础
    • String
  • Java 中级

    • 网络编程
  • Java 高级

    • JVM
    • 多线程
  • Spring
  • SpringMVC
  • SpringBoot
  • MySQL
  • Redis
  • MQ
  • ZooKeeper
  • git
  • linux
  • 设计模式
  • 数据结构与算法
  • 计算机基础
  • Java相关框架
  • 分布式
  • DDD领域驱动设计
  • 系统设计
  • 杂乱无章
Java知识图谱
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

  • String

  • 网络编程

  • JVM

  • 多线程

    • 多线程-线程基础
    • 多线程-AQS
    • 多线程-锁
    • JUC
  • JavaSE
  • 多线程
qouson
2024-05-23

多线程-AQS

# AQS-AbstractQueuedSynchronizer

  • 是什么
    • 字面意思
      • 抽象队列同步器
    • 技术解释
      • 是用来构建锁和其他同步器组件的重量级基础框架以及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量state表示持有锁的状态 20210319152210 CLH:Craig、Landin and Hagersten队列,是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列FIFO
  • AQS为什么是JUC内容中的基石
    • JUC常用类都使用了Sync类,也即是AQS的实现子类 20210319152728
      • ReentrantLock
      • CountDownLatch
      • CycleBarrier
      • Semaphore
      • ReentrantReadWriteLock
      • ...
    • 锁和同步器底层都是AQS的抽象实现
      • 锁,面向锁的使用者 -- 定义了程序员和锁交互的使用层api,隐藏实现细节,调用即可。
      • 同步器,面向锁的实现者 -- Java并发大神DougLee,提供统一规范并简化了锁的实现,屏蔽了同步状态管理,阻塞线程排队和通知唤醒机制等。
  • 能干嘛
    • 加锁会导致阻塞
      • 有阻塞就要排队,实现排队必然需要有某种形式的队列来管理
    • 解释
      • 抢到资源的线程直接使用处理业务逻辑,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能到候客区等候)。但等候线程仍然保留获取锁的可能且获取锁流程仍然继续(候客区的顾客也在等着被叫号,轮到了再去受理窗口办理业务)
      • 如果共享资源被占用,就需要一定的阻塞唤醒机制来保证锁分配。这个机制主要是用CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这就是AQS的抽象表现。它将请求共享资源的线程封装成节点(Node),通过CAS,LockSupport.park()的方式,维护state的状态,使并发达到同步的控制效果。
  • 认识AQS
    • AQS初始

      • 官网解释 20210319155114
      • 有阻塞就需要排队,要排队就需要队列--AQS使用一个volatile的int类型的变量来表示同步状态,使用FIFO队列来完成资源获取的排队工作,将每个要抢占资源的线程封装成一个Node来实现锁的分配,通过CAS来完成对state的修改。
    • AQS内部体系架构 20210319163436

      • AQS自身
        • AQS的int变量
          • AQS的同步状态state成员变量 20210319164154
          • 银行办理业务的受理窗口状态
            • 0就是没有人办理业务
            • 大于等于1,有人占用窗口
        • AQS的CLH队列
          • AQS中的队列是CLH队列(由三个大牛的名字组成)一个变种的双向队列
          • 银行候客区的顾客
        • 小总结
          • 有阻塞就要排队,有排队就要有队列
          • state变量+CLH变种的双端队列
      • 内部类Node(Node类在AQS类的内部)
        • Node的int变量

          • volatile int waitStatus
          • 等候区其他顾客(其他线程)的等待状态
          • 队列中每一个排队的个体就是一个Node
        • Node类的解释

          • 内部结构
          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;
            /** 等待condition唤醒 */
            static final int CONDITION = -2;
            /** 共享式同步状态获取将会无条件地传播下去 */
            static final int PROPAGATE = -3;
          
            /** 初始为0,值为以上 */
            volatile int waitStatus;
          
            /** 前驱节点 */
            volatile Node prev;
          
            /** 后继节点 */
            volatile Node next;
          
            /**
            * The thread that enqueued this node.  Initialized on
            * construction and nulled out after use.
            */
            volatile Thread thread;
          
            /** 下一个等待线程 */
            Node nextWaiter;
          
            /**
            * Returns true if node is waiting in shared mode.
            */
            final boolean isShared() {
                return nextWaiter == SHARED;
            }
          
            /**
            * Returns previous node, or throws NullPointerException if null.
            * Use when predecessor cannot be null.  The null check could
            * be elided, but is present to help the VM.
            *
            * @return the predecessor of this node
            */
            final Node predecessor() throws NullPointerException {
                Node p = prev;
                if (p == null)
                    throw new NullPointerException();
                else
                    return p;
            }
          
            Node() {    // Used to establish initial head or SHARED marker
            }
          
            Node(Thread thread, Node mode) {     // Used by addWaiter
                this.nextWaiter = mode;
                this.thread = thread;
            }
          
            Node(Thread thread, int waitStatus) { // Used by Condition
                this.waitStatus = waitStatus;
                this.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
          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
          59
          60
          61
          62
          63
          64
          65
          66
          67
          68
          • 属性说明 20210319170356
    • AQS同步队列的基本结构 20210319171356

  • 从ReentrantLock解读AQS
    • Loc接口的实现类,基本都是通过聚合一个队列同步器AQS的子类来完成线程访问控制的。
    • ReentrantLock的原理 20210319172723
    • 从lock方法看公平和非公平 20210319172811 20210319172845 20210319172905 20210319173816
      • 以上可以看出公平锁和非公平锁的lock()方法唯一区别就在于公平锁在获取同步状态时多了一个限制条件:
      • hasQueuedPredecessors()这是公平锁加锁时判断等待队列中是否存在有效节点的方法
      • 对比公平锁和非公平锁的tryAcquire()方法实现,差别就在非公平锁获取锁的时候比公平锁少了一个判断!hasQueuedPredecessor()
      • hasQueuedPredecessors()中判断是否需要排队。
      • 公平锁和非公平锁的差异如下:
        • 公平锁:公平锁讲究先到先得,线程在获取锁的时候,如果这个锁的等待队列中已有线程在等待,那么当前线程进入等待队列
        • 非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁。
    • 从非公平锁入手
      • lock()
        • ReentrantLock的加锁过程:
          • 1尝试加锁------------------------tryAcquire(arg)
          • 2加锁失败,线程入队列-------------acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
          • 3线程进入队列后,进入阻塞状态------selfInterrupt()
        • AQS源码解析
          • lock() 20210319202618
          • acquire() 20210319205641
          • tryAcquire(arg) 20210319205917
            • nonfairTryAcquire()
              • true--加锁成功
              • false--加锁失败,入队
          • addWaiter(Node.EXCLUSIVE)
            • 第一个入队,进enq(),初始化一个哨兵节点,用于占位,后自旋添加该节点,Node pred = tail;node.prev = pred;pred.next = node; 20210319210432
            • 第n个入队,直接加入到队尾,Node pred = tail;node.prev = pred;pred.next = node;
          • acquireQueue(addWaiter(Node.EXCLUSIVE),arg) 20210319221010 20210319220840
            • 如果再抢抢失败就进入
              • shouldParkAfterFailedAcquire 20210319222353
                • 如果前驱节点waitStatus是SIGNAL,即shouldParkAfterFailedAcquire返回true,程序继续向下执行parkAndCheckInterrupt(),用于将当前线程挂起
              • parkAndCheckInterrupt 20210319222641
      • unlock()
        • sync.release(1);//将头结点指向第一个节点(哨兵节点的下一个节点,变成哨兵节点,哨兵节点指向null)
        • tryRelease(arg);
        • unparkSuccessor();//发放许可证,唤醒线程去抢锁。
      • 图解 20210319220103 20210319224219
编辑 (opens new window)
上次更新: 2024/05/24, 11:36:46
多线程-线程基础
多线程-锁

← 多线程-线程基础 多线程-锁→

最近更新
01
杂乱无章
12-25
02
基础-大彬
11-14
03
集合-大彬
11-14
更多文章>
Theme by Vdoing | Copyright © 2023-2025 qouson
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式