Java并发之AQS详解

// 如获取资源成功,直接返回

if (!tryAcquire(arg) &&

// 如获取资源失败,将线程包装为Node添加到队列中阻塞等待

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

// 如阻塞线程被打断

selfInterrupt();

}

复制代码

acquire核心为tryAcquireaddWaiteracquireQueued三个函数,其中tryAcquire需具体类实现。 每当线程调用acquire时都首先会调用tryAcquire,失败后才会挂载到队列,因此acquire实现默认为非公平锁

addWaiter将线程包装为独占节点,尾插式加入到队列中,如队列为空,则会添加一个空的头节点。值得注意的是addWaiter中的enq方法,通过CAS+自旋的方式处理尾节点添加冲突。

acquireQueue在线程节点加入队列后判断是否可再次尝试获取资源,如不能获取则将其前驱节点标志为SIGNAL状态(表示其需要被unpark唤醒)后,则通过park进入阻塞状态。

参照流程图,acquireQueued方法核心逻辑为for(;;)shouldParkAfterFailedAcquiretail节点默认初始状态为0,当新节点被挂载到队列后,将其前驱即原tail节点状态设为SIGNAL,表示该节点需要被唤醒,返回true后即被park陷入阻塞。for循环直到节点前驱为head后才尝试进行资源获取。

release

release流程较为简单,尝试释放成功后,即从头结点开始唤醒其后继节点,如后继节点被取消,则转为从尾部开始找阻塞的节点将其唤醒。阻塞节点被唤醒后,即进入acquireQueued中的for(;;)循环开始新一轮的资源竞争。

共享锁分析

acquireShared & releaseShared

public final void acquireShared(int arg) {

// 负数表示获取共享锁失败,不同于tryAcquire的bool返回

if (tryAcquireShared(arg) < 0)

doAcquireShared(arg);

}

public final boolean releaseShared(int arg) {

if (tryReleaseShared(arg)) {

doReleaseShared();

return true;

}

return false;

}

复制代码

acquireSharedreleaseShared整体流程与独占锁类似,tryAcquireShared获取失败后以Node.SHARED挂载到队尾阻塞,直到队头节点将其唤醒。在doAcquireShared与独占锁不同的是,由于共享锁是可以被多个线程获取的,因此在首个阻塞节点被唤醒后,会通过setHeadAndPropagate传递唤醒后续的阻塞节点。

// doAcquireShared核心代码

final Node node = addWaiter(Node.SHARED);

for (;😉 {

final Node p = node.predecessor();

if (p == head) {

int r = tryAcquireShared(arg);

if (r >= 0) {

// r>=0 表示获取锁成功,调整头结点并传递唤醒

setHeadAndPropagate(node, r);

}

}

}

复制代码

setHeadAndPropagatedoReleaseShared构成共享锁唤醒的核心逻辑。

这两方法的逻辑较为简单,不再进行展开,主要对setheadAndPropagate的多节点唤醒判断逻辑做出分析。

进入setHeadAndPropagate,首先需要明确的是,该函数的传入参数propagate一定是非负数,接下来其唤醒主要为两个判断逻辑:

  • 如果propagate > 0,表示存在多个共享锁可以获取,可直接进行doReleaseShared唤醒阻塞节点。

  • 如果propagate = 0,表示仅当前节点可被唤醒,则有两种情况:

  • h == null || h.waitStatus < 0,通常情况下h != null,现给出h.waitStatus < 0的场景。

  • (h = head) == null || h.waitStatus < 0的场景执行序列如下:

独占锁共享锁小结

1、独占锁共享锁默认都是非公平获取策略,可能被插队。

2、独占锁只有一个线程可获取,其他线程均被阻塞在队列中;共享锁可以有多个线程获取。

3、独占锁释放仅唤醒一个阻塞节点,共享锁可以根据可用数量,一次唤醒多个阻塞节点

ConditionObject

AQSNode除了组成阻塞队列外,还在ConditionObject中得到应用,ConditionObject的核心定义为:

public class ConditionObject implements Condition, java.io.Serializable {

private transient Node firstWaiter;

private transient Node lastWaiter;

}

复制代码

ConditionObject通过Node也构成了一个FIFO的队列,那么ConditionObjectAQS提供了怎样的功能呢?

public interface Condition {

void await() throws InterruptedException;

void signal();

void signalAll();

}

复制代码

查看Condition接口的定义,可以看到其定义的方法与Object类的wait/notify/notifyAll功能是一致的。

Synchronized详解中笔者曾对ObjectMonitor做过简单介绍,其中ObjectMonitor包含_WaitSet_EntryList两个队列,分别用于存储wait调用sychronized锁竞争时挂起的线程,而AQS通过ConditionObject同样也提供了wait/notify机制的阻塞队列。


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

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

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

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

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

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

img

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

image.png

image.png

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
33%;" />

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

[外链图片转存中…(img-p0FpZg2M-1712050141717)]

[外链图片转存中…(img-2Za4jLWh-1712050141717)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值