浅学AQS

AbstractQueuedSynchronizer

      • 1、概念
        • 1.1、什么是AQS?
      • 2、AQS原理
        • 2.1同步状态
        • 2.2、CLH队列
        • 2.3、Node内部类
      • 3、流程概述
        • 3.1、入队
        • 3.2、出队
        • 3.3、条件变量
      • 4、模板
        • 4.1、独占式获取资源
        • 4.2、独占式释放资源
        • 4.2、共享式获取资源
        • 4.4、共享式释放资源

1、概念

1.1、什么是AQS?
  AQS是一个抽象的队列同步器,通过维护一个==共享资源状态==和一个==先进先出==的线程等待队列来实现一个多线程访问共享资源的同步框架。
  具体实现中,ReentrantLock是独占式,Semaphore和CountDownLatch是共享式。

2、AQS原理

浅学AQS_第1张图片
  AQS由三部分组成,state同步状态、Node组成的CLH队列、ConditionObject条件变量(包含Node组成的条件单向队列)

2.1同步状态

  在 AQS 中维护了一个同步状态变量state,getState 函数获取同步状态, setState 、compareAndSetState 函数修改同步状态,对于 AQS 来说,线程同步的关键是对 state 的操作,可以说获取、释放资源是否成功都是由state决定的,比如 state > 0 代表可获取资源,否则无法获取,所以 state 的具体语义由实现者去定义,现有的 ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 定义的 state 语义都不一样。

  • ReentrantLock 的 state 用来表示是否有锁资源
  • ReentrantReadWriteLock 的 state 高16位代表 读 锁状态,低16位代表 写 锁状态
  • Semaphore 的 state 用来表示可用信号的个数
  • CountDownLatch 的 state 用来表示计数器的值
2.2、CLH队列

  CLH 是 AQS 内部维护的 FIFO(先进先出)双端双向队列(方便尾部节点插入),基于链表数据结构,当一个线程竞争资源失败,就会将等待资源的线程封装成一个 Node 节点,通过 CAS 原子操作插入队列尾部,最终不同的 Node 节点连接组成了一个 CLH 队列。
  优点有:

  • 先进先出保证了公平性
  • 非阻塞的队列,通过自旋锁和 CAS 保证节点插入和移除的原子性,实现无锁快速插入
  • 采用了自旋锁思想,所以 CLH 也是一种基于链表的可扩展、高性能、公平的自旋锁
2.3、Node内部类

  Node 是 AQS 的内部类,每个等待资源的线程都会封装成 Node 节点组成 CLH 队列、等待队列。
浅学AQS_第2张图片
waitStatus等待状态:
浅学AQS_第3张图片
nextWaiter特殊标记:

  • Node 在 CLH 队列时,nextWaiter 表示共享式或独占式标记
  • Node 在条件队列时,nextWaiter 表示下个 Node 节点指针

3、流程概述

  线程获取资源失败,封装成 Node 节点从 CLH 队列尾部入队并阻塞线程,某线程释放资源时会把 CLH 队列首部 Node 节点关联的线程唤醒(此处的首部是指第二个节点,后面会细说),再次获取资源。
浅学AQS_第4张图片

3.1、入队

  获取资源失败的线程需要封装成 Node 节点,接着尾部入队,在 AQS 中提供 addWaiter 函数完成 Node 节点的创建与入队。

//Node节点入队-CLH队列
    private Node addWaiter(Node mode) {
   
        //根据当前线程创建节点,等待状态为0
        Node node = new Node(Thread.currentThread(), mode);
        // 获取尾节点
        Node pred = tail;
        if (pred != null) {
   
            //如果尾节点不等于null,把当前节点的前驱节点指向尾节点
            node.prev = pred;
            //通过cas把尾节点指向当前节点
            if (compareAndSetTail(pred, node)) {
   
                //之前尾节点的下个节点指向当前节点
                pred.next = node;
                return node;
            }
        }
        //如果添加失败或队列不存在,执行end函数
        enq(node);
        return node;
    }

  添加节点的时候,如果从 CLH 队列已经存在,通过 CAS 快速将当前节点添加到队列尾部,如果添加失败或队列不存在,则指向 enq 函数自旋入队。

//自旋cas入队
	private Node enq(final Node node) {
   
        for (;

你可能感兴趣的:(笔记,java)