AbstractQueuedSynchronizer 原理解析

339 阅读4分钟

「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战」。

AQS 特征与名词

  • 公平锁
  • 可重入
  • LockSupport
  • 自旋锁(CAS)
  • 数据结构之链表
  • 设计模式之模板方法

什么是 AQS ?

字面解释:

抽象的队列同步器

核心类:

  • AbstractOwnableSynchronizer
  • AbstractQueuedLongSynchronizer
  • AbstractQueuedSynchronizer

通常 AbstractQueuedSynchronizer 简称 AQS

技术面解释

是用来构建锁或者其他同步器的组件的重量级基础框架以及整个 JUC 体系的基石,通过内置的 FIFO 队列来完成资源获取线程的排队工作,并通过一个 int 类型变量表示持有锁的状态

AQS 为什么是 JUC 内容的最重要基石

相关的类

  • ReentrantLock

  • CountDownLatch

  • ReentrantReadWriteLock

  • Semaphore

深入理解

  • 锁, 面向锁的使用者, 核心是定义了程序和锁的交互 API
  • 同步器,面向锁的实现者, 比如 Java 并发大神 Doug Lee , 提出同一规范并且简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。

AQS 作用

加锁会导致阻塞

有阻塞就需要排队,实现排队必然需要某种形式的队列来进行管理。

解释说明

抢到资源的线程直接使用处理业务逻辑,抢不到资源的必然进入一种排队等候机制。抢占资源失败的线程去等待(类似银行业务遍历窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等待线程仍然保持获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等待着叫号,轮到了再去受理窗口办理业务)。

既然提到了排队等候机制。 那么就一定会有某种队列形成,这样的队列是什么数据结构呢?

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

AQS 实践

AQS 初步认识

有阻塞就需要有排队,实现排队必须需要队列

AQS 使用一个 volatile 的 int 类型的成员变量来表示同步状态,通过内置的 FIFO 队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个 Node 节点来实现锁的分配,通过 CAS 完成对 State 值的修改。

AQS 内部体系架构

AQS 组成

  • AQS 的 int 变量

AQS 同步状态 State 成员变量

    /**
     * The synchronization state.
     */
    private volatile int state;

银行办理业务的受理窗口状态

1、0 就是没人, 自由状态可以办理

2、大于等于 1 , 有人占用窗口,等着去

  • AQS 的 CLH 队列

CLH 队列(三个大牛的名字组成),为一个双向队列

银行候客区的等待顾客

  • 小总结

有阻塞就需要排队,实现排队必然需要队列

state + CLH 变种的双端队列

AQS 同步队列的基本结构

  • Node 中的 int 变量

Node 的等待状态 waitState 成员变量

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;

        // 当前线程
        volatile Thread thread;


        Node nextWaiter;


        final boolean isShared() {
            return nextWaiter == SHARED;
        }


        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;
        }
    }

AQS 同步队列的基本结构