// 如获取资源成功,直接返回
if (!tryAcquire(arg) &&
// 如获取资源失败,将线程包装为Node添加到队列中阻塞等待
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 如阻塞线程被打断
selfInterrupt();
}
复制代码
acquire
核心为tryAcquire
、addWaiter
和acquireQueued
三个函数,其中tryAcquire
需具体类实现。 每当线程调用acquire
时都首先会调用tryAcquire
,失败后才会挂载到队列,因此acquire
实现默认为非公平锁。
addWaiter
将线程包装为独占节点,尾插式加入到队列中,如队列为空,则会添加一个空的头节点。值得注意的是addWaiter
中的enq
方法,通过CAS+自旋
的方式处理尾节点添加冲突。
acquireQueue
在线程节点加入队列后判断是否可再次尝试获取资源,如不能获取则将其前驱节点标志为SIGNAL
状态(表示其需要被unpark
唤醒)后,则通过park
进入阻塞状态。
参照流程图,acquireQueued
方法核心逻辑为for(;;)
和shouldParkAfterFailedAcquire
。tail
节点默认初始状态为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;
}
复制代码
acquireShared
和releaseShared
整体流程与独占锁类似,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);
}
}
…
}
复制代码
setHeadAndPropagate
和doReleaseShared
构成共享锁唤醒的核心逻辑。
这两方法的逻辑较为简单,不再进行展开,主要对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
AQS
中Node
除了组成阻塞队列外,还在ConditionObject
中得到应用,ConditionObject
的核心定义为:
public class ConditionObject implements Condition, java.io.Serializable {
…
private transient Node firstWaiter;
private transient Node lastWaiter;
…
}
复制代码
ConditionObject
通过Node
也构成了一个FIFO
的队列,那么ConditionObject
为AQS
提供了怎样的功能呢?
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开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
惊喜
最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)
-
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,
点击传送门即可获取!
- 33%;" />
惊喜
最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)
[外链图片转存中…(img-p0FpZg2M-1712050141717)]
[外链图片转存中…(img-2Za4jLWh-1712050141717)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!