图解AQS源码分析

本文详细解释了AQS线程队列在ReentrantLock中的应用,包括公平锁与非公平锁的实现机制,多线程抢锁的步骤,以及关键方法如CAS、park和unpark的使用。
摘要由CSDN通过智能技术生成

2 AQS线程队列

3 ReentrantLock实例构建

4 多线程抢锁图示(非公平锁)

4.1 A线程加锁

4.2 B线程加锁

4.3 C线程加锁

4.4 A线程解锁

4.5 B线程解锁

4.6 C线程解锁

5 源码分析(以非公平锁为例)

5.1 线程A先抢占锁

5.2 线程A释放锁


1 简介

====

AbstractQueuedSynchronizer抽象的队列式同步器(抽象类)。提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLock、CountDownLatch等。

其中比较重要的概念有:

  • state共享资源

  • FIFO

  • CAS

  • park  unpark

2 AQS线程队列

=========

Node是整个队列中最核心的部分,包含CANCELLED、SIGNAL、CONDITION、PROPAGATE四种状态、指向前后节点的指针、thread是节点存储的值。

ReentrantLock中的公平锁与非公平锁都继承了这个抽象类。

ReentrantLock包含公平锁和非公平锁,每种锁里面还包含了抽象的锁Sync,抽象锁执行AQS。

如果当前是线程1拥有锁的话,线程2,3,4会调用park操作来被挂起加入队列中。

当拥有锁的Thread1执行完毕后会调用unpark方法,head指向下一个节点。由于ReentrantLock是一个可重入锁,重入次数被AQS.state记录,每重入一次值 + 1,退出一次 - 1。

3 ReentrantLock实例构建

===================

ReentrantLock默认构造函数创建的是非公平锁,如果想要公平锁的话需要在构造方法中传入true。

4 多线程抢锁图示(非公平锁)

===============

线程抢到锁的过程使用CAS实现。

初始时等待队列只有一个Head节点,其中存储的Thread是null,用来占位,线程B,C在队列中使用双向链表的方式与Head相连,调用park挂起。

上图的分步过程见下文。

4.1 A线程加锁


A线程抢锁时通过CAS操作将state改为1,并将AQS的exclusiveOwnerThread属性设为A,表示当前锁的拥有者。

4.2 B线程加锁


此时B线程要进行抢锁,发现state已经是1了,所以CAS操作失败,进入到队列中,waitStatus设置为0

第一次new Node的时候,即创建head,thread设置为了null,waitStatus设置为SIGNAL

4.3 C线程加锁


接着C线程抢锁时,操作同B,会将B的waitStatus设置为SIGNAL,自己的是初始化是的值0

新插入的节点waitStatus值都设为0原因是用来标记队列中最后一个节点,此时不必再进行对后续节点的unpark操作,等待队列中没有节点时会有一个null的节点用作占位,这两个操作都是防止等待队列中没有等待的线程时而变为空。

4.4 A线程解锁


A线程进行解锁操作之后,B线程可以抢到锁,流程如下:

A线程解锁操作:

  • state设为0

  • exclusiveOwnerThread设为null表示锁被释放

  • head节点向后移动一位进行unpark操作唤醒线程B

  • 之前的head节点要被释放

4.5 B线程解锁


4.6 C线程解锁


5 源码分析(以非公平锁为例)

===============

5.1 线程A先抢占锁


构造方法:

public ReentrantLock() {

sync = new NonfairSync();

}

lock():

public void lock() {

sync.lock();

}

假设现在有两个线程A和B,如下:

static final class NonfairSync extends Sync {

private static final long serialVersionUID = 7316153563782823691L;

// 线程A进来

// 线程B进来

final void lock() {

// 线程A执行成功,将AQS.state置为1,表示已抢占该锁

// 线程B,由于线程A已将AQS.state置为1,所以线程B执行CAS操作为false

if (compareAndSetState(0, 1))

// 线程A,将AbstractOwnableSynchronizer.exclusiveOwnerThread设置为当前线程

setExclusiveOwnerThread(Thread.currentThread());

else

// 线程B执行该部分,尝试获取一个锁

acquire(1);

}

protected final boolean tryAcquire(int acquires) {

return nonfairTryAcquire(acquires);

}

}

其中acquire(1)方法的源码如下:

// 线程B调用:arg = 1

public final void acquire(int arg) {

// 线程B

// 尝试获得非公平锁,由于已被线程A抢到,所以tryAcquire(arg) = false

// addWaiter方法,构建承载线程B的Node,然后添加到队列末尾,返回该node

// acquireQueued方法

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg)) {

// 设置当前线程的中断标识

selfInterrupt();

}

}

尝试抢锁的方法 tryAcquire(arg) 最终调用的:

/**

  • 尝试抢锁

  • 处理内容:

  • 1 如果抢到锁,返回true

  •  1.1 如果当前线程第一次抢到锁:
    
  •      AQS.status由0变为1
    
  •      AQS.exclusiveOwnerThread = Thread.currentThread()
    
  •      返回true
    
  •  1.2 如果当前线程再次抢到锁(重入)
    
  •      AQS.status++
    
  •      返回true
    
  • 2 没抢到锁,返回false

*/

// 线程B acquires = 1

final boolean nonfairTryAcquire(int acquires) {

final Thread current = Thread.currentThread();

// 由于线程A已将state设为1,所以c=1

int c = getState();

if (c == 0) { // 线程B,不满足不执行

if (compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

else if (current == getExclusiveOwnerThread()) { // 线程B,不满足不执行

/**

  • 获得当前独享线程,如果就是当前线程,那么执行重入操作

  • 执行tryLock()时:

  •  如果第二次进入,则nextc = 0 + 1 = 1
    
  •  如果第三次进入,则nextc = 1 + 1 = 2
    
  •  如果第四次进入,则nextc = 2 + 1 = 3
    

*/

int nextc = c + acquires;

if (nextc < 0) // overflow

throw new Error(“Maximum lock count exceeded”);

setState(nextc);

return true;

}

// 线程B,返回false

return false;

}

回到 acquire 代码中 addWaiter 方法:

// 线程B:mode = Node.EXCLUSIVE = null

private Node addWaiter(Node mode) {

Node node = new Node(Thread.currentThread(), mode);

Node pred = tail;

// 线程B:由于线程A并没有进入链表,所以tail = null不会进入if方法

if (pred != null) {

node.prev = pred; // (老的尾部node) <— 新node.prev

if (compareAndSetTail(pred, node)) {

pred.next = node; // (老的尾部node) next —> (新node)

return node;

}

}

// 线程B:node = 承载线程B的node

enq(node);

return node;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

言尽于此,完结

无论是一个初级的 coder,高级的程序员,还是顶级的系统架构师,应该都有深刻的领会到设计模式的重要性。

  • 第一,设计模式能让专业人之间交流方便,如下:

程序员A:这里我用了XXX设计模式

程序员B:那我大致了解你程序的设计思路了

  • 第二,易维护

项目经理:今天客户有这样一个需求…

程序员:明白了,这里我使用了XXX设计模式,所以改起来很快

  • 第三,设计模式是编程经验的总结

程序员A:B,你怎么想到要这样去构建你的代码

程序员B:在我学习了XXX设计模式之后,好像自然而然就感觉这样写能避免一些问题

  • 第四,学习设计模式并不是必须的

程序员A:B,你这段代码使用的是XXX设计模式对吗?

程序员B:不好意思,我没有学习过设计模式,但是我的经验告诉我是这样写的

image

从设计思想解读开源框架,一步一步到Spring、Spring5、SpringMVC、MyBatis等源码解读,我都已收集整理全套,篇幅有限,这块只是详细的解说了23种设计模式,整理的文件如下图一览无余!

image

搜集费时费力,能看到此处的都是真爱!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
求…

程序员:明白了,这里我使用了XXX设计模式,所以改起来很快

  • 第三,设计模式是编程经验的总结

程序员A:B,你怎么想到要这样去构建你的代码

程序员B:在我学习了XXX设计模式之后,好像自然而然就感觉这样写能避免一些问题

  • 第四,学习设计模式并不是必须的

程序员A:B,你这段代码使用的是XXX设计模式对吗?

程序员B:不好意思,我没有学习过设计模式,但是我的经验告诉我是这样写的

[外链图片转存中…(img-AM4x0UKc-1712892328027)]

从设计思想解读开源框架,一步一步到Spring、Spring5、SpringMVC、MyBatis等源码解读,我都已收集整理全套,篇幅有限,这块只是详细的解说了23种设计模式,整理的文件如下图一览无余!

[外链图片转存中…(img-QZ2Ab2qF-1712892328027)]

搜集费时费力,能看到此处的都是真爱!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值