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

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

- AQS自身
- AQS的int变量
- AQS的同步状态state成员变量

- 银行办理业务的受理窗口状态
- 0就是没有人办理业务
- 大于等于1,有人占用窗口
- AQS的同步状态state成员变量
- AQS的CLH队列
- AQS中的队列是CLH队列(由三个大牛的名字组成)一个变种的双向队列
- 银行候客区的顾客
- 小总结
- 有阻塞就要排队,有排队就要有队列
- state变量+CLH变种的双端队列
- AQS的int变量
- 内部类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- 属性说明

- AQS自身
AQS同步队列的基本结构

- 从ReentrantLock解读AQS
- Loc接口的实现类,基本都是通过聚合一个队列同步器AQS的子类来完成线程访问控制的。
- ReentrantLock的原理

- 从lock方法看公平和非公平
- 以上可以看出公平锁和非公平锁的lock()方法唯一区别就在于公平锁在获取同步状态时多了一个限制条件:
- hasQueuedPredecessors()这是公平锁加锁时判断等待队列中是否存在有效节点的方法
- 对比公平锁和非公平锁的tryAcquire()方法实现,差别就在非公平锁获取锁的时候比公平锁少了一个判断!hasQueuedPredecessor()
- hasQueuedPredecessors()中判断是否需要排队。
- 公平锁和非公平锁的差异如下:
- 公平锁:公平锁讲究先到先得,线程在获取锁的时候,如果这个锁的等待队列中已有线程在等待,那么当前线程进入等待队列
- 非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁。
- 从非公平锁入手
- lock()
- ReentrantLock的加锁过程:
- 1尝试加锁------------------------tryAcquire(arg)
- 2加锁失败,线程入队列-------------acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- 3线程进入队列后,进入阻塞状态------selfInterrupt()
- AQS源码解析
- lock()

- acquire()

- tryAcquire(arg)
- nonfairTryAcquire()
- true--加锁成功
- false--加锁失败,入队
- nonfairTryAcquire()
- addWaiter(Node.EXCLUSIVE)
- 第一个入队,进enq(),初始化一个哨兵节点,用于占位,后自旋添加该节点,Node pred = tail;node.prev = pred;pred.next = node;

- 第n个入队,直接加入到队尾,Node pred = tail;node.prev = pred;pred.next = node;
- 第一个入队,进enq(),初始化一个哨兵节点,用于占位,后自旋添加该节点,Node pred = tail;node.prev = pred;pred.next = node;
- acquireQueue(addWaiter(Node.EXCLUSIVE),arg)
- 如果再抢抢失败就进入
- shouldParkAfterFailedAcquire
- 如果前驱节点waitStatus是SIGNAL,即shouldParkAfterFailedAcquire返回true,程序继续向下执行parkAndCheckInterrupt(),用于将当前线程挂起
- parkAndCheckInterrupt

- shouldParkAfterFailedAcquire
- 如果再抢抢失败就进入
- lock()
- ReentrantLock的加锁过程:
- unlock()
- sync.release(1);//将头结点指向第一个节点(哨兵节点的下一个节点,变成哨兵节点,哨兵节点指向null)
- tryRelease(arg);
- unparkSuccessor();//发放许可证,唤醒线程去抢锁。
- 图解

- lock()
编辑 (opens new window)
上次更新: 2024/05/24, 11:36:46