Java高并发学习(七)——AQS及源码解析 详细图解哦!!

AQS

AQS 是什么

在这里插入图片描述通常的,AbstractQueueSynchronizer类简称AQS。

AbstractQueueSynchronizer字面意思是: 抽象队列 同步器
抽象的,AbstractQueueSynchronizer是JUC的基石,其自身是抽象类,提供模板,供子类实现。

public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable

队列
队列的实现形式有单链表和双链表,AbstractQueueSynchronizer使用双链表。队列是对抢不到锁的线程的管理。

同步器

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

AQS 的作用

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

为什么AQS 是JUC的基石

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
可以看到很多的锁(ReentrantLock、ReentrantReadWriteLock等)和同步组件(CountDownLatch、Semaphore信号量)都使用到了AQS,所以说他是JUC的 基石自然而然啦。

抢到资源的线程直接使用处理业务逻辑,抢不到的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待,但等候的线程仍然保留获取锁的可能且获取锁的流程仍在继续,

这个获取锁的流程由CLH队列的变体来实现。它将暂时获取不到锁的线程加入到 队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的节点(Node),通过CAS、自旋以及LockSupport.park()的方式,维护state变量的状态,是并发达到同步控制的效果。

AbstractQueuedSynchronizer源码分析

AQS组成

在这里插入图片描述
AQS主要包括两部分:

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

CLH: 三个大牛组成的名字,是一个单向链表,AQS中的队列使用的是CLH辩题的虚拟双向队列FIFO。

Node内部类

AbstractQueuedSynchronizer有个内部类Node,这个Node的元素是Thread,相当于Node< Thread >。这个与Map类似,Map是通过数组实现的,数组的元素时Node< k, v>。

Node类的内部结构

在这里插入图片描述其中waitStatus是排队队列中每个线程的状态,相当于定后驱每个客户的等待状态。取值为上面的枚举值。

Node类的属性含义

在这里插入图片描述

在这里插入图片描述

AQS同步队列的基本结构

在这里插入图片描述

int整型变量state

state表示同步状态,相当于银行办理业务的受理窗口的状态。
0表示该窗口没线程占用.
1表示该窗口有线程占用,需要等待,

在这里插入图片描述

以ReentrantLock非公平锁为例来理解 AQS

ReentrantLock的内部结构

在这里插入图片描述

在这里插入图片描述

1.A线程进入acquire(int arg)方法

在这里插入图片描述
在这里插入图片描述所以调用AbstractQueuedSynchronizer 的 acquire(int arg)就是调用子类NonfairSync的tryAcquire(int acquires)

1.1 首先调用 nonfairTryAcquire(int acquires)尝试获取非公平锁

也就是 nonfairTryAcquire(1)尝试获取锁

在这里插入图片描述一路返回true,最终执行结果
在这里插入图片描述

2.线程B进入acquire(int arg)方法

在这里插入图片描述

if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();

首先调用 tryAcquire(arg),也就是tryAcquire(1)尝试获取锁

2.1 调用 nonfairTryAcquire(int acquires)尝试获取非公平锁

在这里插入图片描述
由于 tryAcquire(arg) 返回结果是false,所以!tryAcquire(arg) 为true,所以acquire(int arg)方法中的判断 继续执行。

if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();

2.2 addWaiter(Node.EXCLUSIVE)将B线程包装成Node节点

阻塞队列初始状态:
在这里插入图片描述

2.2.1 初始化阻塞队列

线程B进入addWaiter(Node mode)方法,调用addWaiter(Node.EXCLUSIVE)
在这里插入图片描述

initializeSyncQueue()方法具体实现:
在这里插入图片描述
队列初始化后的结果:
在这里插入图片描述
for循环还没有走完哦!

2.2.2 将封装了线程B的node节点加入阻塞队列

在这里插入图片描述

compareAndSetTail(Node expect, Node update) 方法实现如下:
在这里插入图片描述

修改后等待队列如下:

在这里插入图片描述

2.3 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

2.3.1 队首元素先尝试获取锁

一开始B节点的predecessor为head,所以会尝试获取锁,tryAcquire(int acquires)
在这里插入图片描述
调用tryAcquire(int acquires)即调用nonfairTryAcquire(int acquire)
在这里插入图片描述

进入shouldParkAfterFailedAcquire(Node pred, Node node)
在这里插入图片描述
上述的pred.compareAndSetWaitStatus(ws, Node.SIGNAL); 将waitstatus设置为-1。

for循环没结束哦! 所以会再判断 if (shouldParkAfterFailedAcquire(p, node))

在这里插入图片描述再次进入shouldParkAfterFailedAcquire(Node pred, Node node)

在这里插入图片描述在这里插入图片描述
B线程进入阻塞状态

3.线程C进入acquire(int arg)方法

3.1 首先调用 nonfairTryAcquire(int acquires)尝试获取非公平锁

尝试获取锁,nonfairTryAcquire(1)

在这里插入图片描述

3.2 创建C节点

将线程C封装到 节点的过程与B类似,这里就不赘述了

在这里插入图片描述

3.3 将C节点加入阻塞队列过程

在这里插入图片描述
第一次进入shouldParkAfterFailedAcquire(Node pred, Node node)即调用 shouldParkAfterFailedAcquire(p, node)时,

在这里插入图片描述

第二次进入shouldParkAfterFailedAcquire(Node pred, Node node)即调用 shouldParkAfterFailedAcquire(p, node)时,

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.A线程释放锁

在这里插入图片描述

AbstractQueuedSynchronizer抽象类的release()方法

在这里插入图片描述

4.1 A线程调用tryRelease(int realeases)释放掉锁

调用ReentrantLock的tryRelease(1)
在这里插入图片描述

4.2 唤醒阻塞队列中的队首线程

在这里插入图片描述

unpark()唤醒B线程
在这里插入图片描述

5. B线程被唤醒并抢占锁

B线程被唤醒,由Waiting状态进入Running状态,从阻塞处开始执行
在这里插入图片描述

B线程在for循环中,调用tryAcquire(1)
在这里插入图片描述

5.1 B线程调用nonfairTryAcquire(1)尝试获取锁

在这里插入图片描述

5.2 B线程获取锁成功,释放阻塞队列中的内容

进入setHead(Node)方法,即调用setHeadnode()
在这里插入图片描述
修改后的阻塞队列

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

B线程工作时间结束后释放锁,然后C线程抢占锁的过程与B线程被A线程唤醒抢占锁类似。这里就不追溯了,打个断点自己调试一下,大概就清除了、

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
AQS(AbstractQueuedSynchronizer)是Java并发编程中的一个重要类,它可以理解为抽象的队列同步器。AQS提供了一种基于FIFO队列的同步机制,用于实现各种同步器,如ReentrantLock、CountDownLatch、Semaphore等。 AQS的核心思想是使用一个volatile的int类型变量state来表示同步状态,通过CAS(Compare and Swap)操作来实现对state的原子更新。AQS内部维护了一个双向链表,用于保存等待获取同步状态的线程。 AQS的具体实现包括以下几个方面: 1. 内部属性:AQS内部有两个重要的属性,一个是head,表示队列的头节点;另一个是tail,表示队列的尾节点。 2. 入队操作:AQS的入队操作是通过enq方法实现的。在入队操作中,首先判断队列是否为空,如果为空,则需要初始化队列;否则,将新节点添加到队列的尾部,并更新tail指针。 3. CAS操作:AQS的CAS操作是通过compareAndSetHead和compareAndSetTail方法实现的。这些方法使用CAS操作来更新head和tail指针,保证操作的原子性。 4. 出队操作:AQS的出队操作是通过deq方法实现的。在出队操作中,首先判断队列是否为空,如果为空,则返回null;否则,将头节点出队,并更新head指针。 5. 同步状态的获取和释放:AQS提供了acquire和release方法来获取和释放同步状态。acquire方法用于获取同步状态,如果获取失败,则会将当前线程加入到等待队列中;release方法用于释放同步状态,并唤醒等待队列中的线程。 通过继承AQS类,可以实现自定义的同步器。具体的实现方式是重写AQS的几个关键方法,如tryAcquire、tryRelease等,来实现对同步状态的获取和释放。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值