> 文章列表 > JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

1. 前置知识

  • 公平锁和非公平锁
  • 可重入锁
  • 自旋锁
  • LockSupport
  • 数据结构之双向链表
  • 设计模式之模板设计模式

AQS重要性

JAVA ------>JVM

AQS ------>AQS

2. AQS入门级别理论知识

2.1 是什么?

2.1.1 字面意思

Abstract Queued Synchronizer----抽象的队列同步器

源码位置:

AbstractQueuedSynchronizer和AbstractQueuedLongSynchronizer是AbstractOwnableSynchronizer的子类

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

并且是一个抽象类:

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

2.1.2 技术解释

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

  • 是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石,主要用于解决锁分配给"谁"的问题
  • 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量
    表示持有锁的状态

CLH队列(FIFO)

CLH:Craig、Landin and Hagersten 队列,是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列FIFO

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

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

2.2.1 和AQS关联的技术

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

JUC的以下技术都是以AQS为基石

  • Semaphore
  • CycleBarrier
  • ReentranReadWriteLock
  • CountDownLatch
  • ReentranLock

AQS子类:

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

2.2.2 锁和同步器的关系

  • 锁,面向锁的使用者
    • 定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可。
  • 同步器,面向锁的实现者
    • 比如Java并发大神DougLee,提出统一规范并简化了锁的实现,
      屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。

2.3 AQS能干嘛?

因为:加锁会导致阻塞

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

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

3 AQS内部体系架构

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

3.1 AQS自身

3.1.1 AQS的int变量state-volatile修饰

代表同步状态标志位

  • 0代表资源没有被线程占用,资源现在是空闲状态
  • 1代表资源被线程占用,资源现在不是空闲状态

3.2 内部类Node(Node类在AQS类内部)

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

3.2.1 Node对象两种模式

  • SHARED(共享模式)
    • 标志节点正在等待共享模式的标记
  • EXCLUSIVE(独占模式)
    • 标志节点正在独占模式等待的标记

3.2.2 Node的Node的int变量waitState-volatile修饰

每个Node节点在AQS队列中的等待状态,Node初始化时waitState为0

  • CANCELLED(取消)=1
    • 表示该Node节点为取消状态,需要出队
  • SIGNAL(标志)=-1
    • 表示该节点的后一个节点需要unparking(LockSupport里的知识)
  • CONDITION(条件)=-2
    • 表示该节点正在等待某种条件激活
  • PROPAGATE(传播)=-3
    • 指示下一个 acquireShared 应该无条件传播的 waitStatus 值

只有CANCELLED状态是大于0的,判断时有需要先记住

3.2.3 prev&next 双向链表

记录该节点的前一个节点和后一个节点

3.3 以上得出结论:AQS同步队列的基本结构

CLH:Craig、Landin and Hagersten 队列,是个单向链表,AQS中的队列是CLH变体的虚拟双向队列(FIFO)

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

4 AQS源码分析之ReentranLock

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

4.1 架构

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

上图可知:

  • ReentranLock实现了Lock接口
  • ReentranLock中的内部类NonfairSync(非公平锁)和FairSync(公平锁)继承了Sync
  • Sync又继承了AQS抽象类

4.2 ReentranLock公平锁与非公平锁

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

上图可以得知:

  • new ReentranLock()和new ReentranLock(false)都是非公平锁
  • new ReentranLock(true) 表示公平锁

4.2.1 公平锁和非公平锁

4.2.1.1 公平锁和非公平锁的lock方法

提前了解acquire方法会调用tryAcquire,不管公平锁还是非公平锁最后都会调用acquire方法

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

  • 因为非公平的的tryAcquire不讲武德,不排队所以我们在抢锁之前需要看看锁的状态status是否为0,不然队列中所有节点不管status是否为0都去强太耗费性能.
  • 而公平锁在tryAcquire的时候将武德,只有成为头结点才会去抢,所以直接acquire

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

4.2.1.2 非公平锁的tryAcquire

非公平锁tryAcquire返回值:

  1. 当AQS status为0返回true
  2. 是重入锁的情况下返回true

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

nonfairTryAcquire()方法

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

4.2.1.3 公平锁的tryAcquire

公平锁tryAcquire返回值:

  1. 当AQS status为0并且当前节点是头结点时返回true
  2. 是重入锁的情况下返回true

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

参考下图及结论:

!hasQueuedPredecessors()的意思就是如果在当前线程之前有排队的线程就不尝试去抢锁了,老老实实的排队,这不就是公平锁吗?

非公平锁没有这个条件约束,所以不管是排在那个位置的线程都会去抢一抢

hasQueuedPredecessors()方法

  • 如果在当前线程之前有排队的线程,则为true ;
  • 如果当前线程位于队列的头部或队列为空,则为false

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

总结:

  • 可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:
    hasQueuedPredecessors():
    hasQueuedPredecessors是公平锁加锁时判断等待队列中是否存在有效节点的方法

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

4.3 以非公平锁为例继续后面的源码分析

tryAcquire方法在4.2章节以及分析后续我们主要针对

  • addWaiter
  • acquireQueued
  • selfInterrupt

以上三个方法进行分析

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

4.3.0 acquire方法总流程

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

4.3.1 addWaiter(Node.EXCLUSIVE)

将加锁失败的节点加入队列

当tryAcquire()返回false的情况下才会执行addWaiter

  • 公平锁:

    以下条件只要包含一个就返回false

    1. status不等于0
    2. 当前节点不是头结点
    3. 也不是可重入锁
  • 非公平锁

    以下条件只要包含一个就返回false

    1. status不等于0
    2. 也不是可重入锁

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

enq(Node node)

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

4.3.2 acquireQueued(final Node node, int arg)

  • 将队列中各节点的等待状态waitStatus进行管理
    • waitStatus为-1的节点park
    • waitStatus为1的移出队列;
    • 其他状态值会逐步修改为-1,
    • 最后将waitStatus为-1的节点对应的线程park
  • 在addWaiter的大自旋中会不断tryAcquire(),检测共享资源是否解锁

addWaiter后会返回最新的尾节点

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

predecessor()方法 返回尾结点的前置节点

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

shouldParkAfterFailedAcquire(Node pred, Node node)

该方法主要作用就是将修改抢锁失败的节点waitStatus为-1等待状态,或者将想要取消的节点从队列中移除
如果前驱节点的 waitStatus 是 SIGNAL状态,即 shouldParkAfterFailedAcquire 方法会返回 true 程序会继续向下执行 parkAndCheckInterrupt 方法,用于将当前线程挂起

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

parkAndCheckInterrupt()

阻塞当前节点

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

4.3.3 selfInterrupt()

抢锁失败,加入队列成功那么就中断当前线程

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

4.4 unlock解锁源码分析

解锁操作,主要作用是将头结点后为等待状态-1的节点unpark,然后唤醒阻塞的线程,以及一些异常处理

unlock主要就是调用AQS的释放方法release

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

release开始会tryAcquire尝试解锁

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

tryAcquire尝试解锁

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

unparkSuccessor方法将中断节点恢复至正常状态

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

设计师之家