AQS内部有一个原子int变量(命名为state),它是AQS的核心状态,也是唯一跟同步有关的变量。例如,ReentrantLock中state≠0表示锁已被占,state=0表示锁空闲。AQS的子类负责赋予state具体含义,通过覆写tryAcquire(), tryRelease(), tryAcquireShared(), tryReleaseShared(), isHeldExclusively()来通过以乐观锁方式对state进行操作。并且,state只允许通过getState(), setState(), compareAndSetState()对其操作以保证可见性和原子性。
2. 用一个CLH队列存放等待线程,每个线程一个结点,分独占模式和共享模式。
CLH队列(其实是CLH队列的变种)是用于实现自旋锁的队列数据结构,主体是一个链表,如下图所示。
CLH队列
在CLH队列中,每个线程的等待状态(waitState变量)保存在前一个结点中,取值可以是 1 (CANCELED,线程已经放弃等待),-1(SIGNAL,线程已经被阻塞、需要被唤醒),-2(CONDITION,线程在条件队列中等待,这种状态的结点不可能出现在CLH队列,只会出现在Condition条件队列上),-3(PROPAGATE,下一个在共享模式中等待的线程无条件唤醒,即唤醒动作可以继续往队列后面接力),0(就绪,这个状态代表线程没有被阻塞,并且准备好请求锁)。
3. 通过循环+CAS操作来对CLH队列进行修改
因为AQS本身就是用于实现锁的,所以AQS中的CLH队列没有锁来保护,且必须支持并发修改。怎么办?通过循环结合CAS操作来实现CLH队列操作的线程安全性。例如enq(),
private Node enq(final Node node) { for (;😉 { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } }}
4. 加锁操作如何实现
与CLH队列操作很类似,加锁操作也是通过循环+CAS操作来实现,不过还使用到了让线程阻塞的方法LockSupport.park()。我们以不可中断的加锁操作为例,讲解其主要实现逻辑如下:在一个死循环中,首先调用乐观锁加锁操作 tryAcquire(),如果成功,则加锁操作直接返回;如果失败了,则判断当前线程结点的前一结点的waitState:如果等于SIGNAL,则直接进入阻塞(说明此时已经有前一个结点得到锁了),如果是CANCELED,则清理一次CLH队列(把已经取消等待的线程中队列中移除)再次执行循环,如果是0或PROPAGATION,则把状态改为SIGNAL,继续执行循环。如果前面的阻塞操作被其他线程唤醒了,再次执行循环。参考代码:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;😉 {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
加锁操作其实有计时版本、可中断版本,但大体逻辑就是上面这样,只不过在循环中再进行了一些时间判断、中断标志判断等,并体现在返回结果或抛出异常上。
总结一下,AQS的加锁操作就是在一个循环中,不断执行CAS加锁(成功则返回,失败则继续),然后不断阻塞和被唤醒(在每次被唤醒的时候,顺便执行一些CLH队列清理工作),再次执行CAS加锁。
5. 加锁操作优化
AQS中的加锁操作,即acquire(),进行了一种barge优化。意思就是,当锁被释放出来以后,此时正好有一个线程请求锁,而队列中的第一个线程也被唤醒并且请求锁,这两个线程谁将获得锁是不确定的。这种优化对AQS锁的并发性有提升,但是使得它变成不公平的锁(没有按照FIFO原则操作)。
public final void acquire(int arg) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性
Java面试宝典2021版
最常见Java面试题解析(2021最新版)
2021企业Java面试题精选
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
转存中…(img-XD75pqSO-1713444830764)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!