跳至主要內容

AbstractQueuedSynchronizer之AQS

Jin...大约 5 分钟

AbstractQueuedSynchronizer之AQS

1、是什么

字面意思:抽象的队列同步器

源代码:

  1. AbstractOwnableSynchronizer
  2. AbstractQueuedLongSynchronizer
  3. AbstractQueuedSynchronizer
image-20220820221155075
image-20220820221155075
  • 是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态

    image-20220820221314393
    image-20220820221314393

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

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

2.1、和AQS有关的

image-20220820221433210
image-20220820221433210

ReentrantLock

image-20220820221613660
image-20220820221613660

CountDownLatch

image-20220820221917811
image-20220820221917811

ReentrantReadWriteLock

image-20220820222119837
image-20220820222119837

Semaphore

image-20220820222226868
image-20220820222226868

2.2、进一步理解锁和同步器的关系

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

3、能干嘛

  1. 加锁会导致阻塞

    • 有阻塞就需要排队,实现排队必然需要队列
  2. 解释说明

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

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

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

    image-20220820222821119
    image-20220820222821119

4、AQS基础

4.1、官网解释

image-20220820223208204
image-20220820223208204

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

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

image-20220820223451479
image-20220820223451479

4.2、AQS内部体系架构

4.2.1、AQS自身

  1. AQS的int变量

    • AQS的同步状态State成员变量

      • private volatile int state;
        
    • 银行办理业务的受理窗口状态

      • 零就是没人,自由状态可以办理
      • 大于等于1,有人占用窗口,等着去
  2. AQS的CLH队列

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

    • image-20220820223742858
      image-20220820223742858
    • 银行候客区的等待顾客

  3. 小总结

    • 有阻塞就需要排队,实现排队必然需要队列
    • state变量+CLH双端队列

4.2.2、内部类Node(Node类在AQS类内部)

  1. Node的int变量

    • Node的等待状态waitState成员变量

      • volatile int waitStatus;
        
      • 等候区其它顾客(其它线程)的等待状态

      • 队列中每个排队的个体就是一个

  2. Node此类

    • image-20220820224434897
      image-20220820224434897

4.3、AQS同步队列的基本结构

image-20220820224543087
image-20220820224543087

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

5、从ReentrantLock解读AQS

Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的

5.1、ReentrantLock的原理

image-20220820224716270
image-20220820224716270

5.2、从最简单的lock方法开始看看公平和非公平

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

5.3、非公平锁走起,方法lock()

lock()

image-20220820225507331
image-20220820225507331

acquire()

image-20220820225521135
image-20220820225521135
image-20220820225526241
image-20220820225526241

tryAcquire(arg)

nonfairTryAcquire(acquires)

image-20220820225619524
image-20220820225619524

addWaiter(Node.EXCLUSIVE)

addWaiter(Node mode)

image-20220820225716810
image-20220820225716810

enq(node);

image-20220820225732385
image-20220820225732385
  • 双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位。
  • 真正的第一个有数据的节点,是从第二个节点开始的。

假如3号ThreadC线程进来

  1. prev
  2. compareAndSetTail
  3. next

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

acquireQueued

image-20220820225901139
image-20220820225901139

假如再抢抢失败就会进入

  1. shouldParkAfterFailedAcquire 和 parkAndCheckInterrupt 方法中
    • image-20220820225935335
      image-20220820225935335
  2. shouldParkAfterFailedAcquire
    • image-20220820225945183
      image-20220820225945183
    • 如果前驱节点的 waitStatus 是 SIGNAL状态,即 shouldParkAfterFailedAcquire 方法会返回 true 程序会继续向下执行 parkAndCheckInterrupt 方法,用于将当前线程挂起
  3. parkAndCheckInterrupt
    • image-20220820230006350
      image-20220820230006350

unlock

  1. sync.release(1);
  2. tryRelease(arg)
  3. unparkSuccessor
贡献者: Jin
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度