本次面试答案,以及收集到的大厂必问面试题分享:
更多JUC源码解读系列文章请持续关注JUC源码解读文章目录JDK8!
文章目录
-
- 一、前言
-
二、共享模式获取锁
-
- 1、doAcquireShared入队列,自旋,阻塞
-
- (1)setHeadAndPropagate传播唤醒后继
-
(2)判断是否应该阻塞和阻塞线程
-
三、共享模式可中断获取锁
-
- doAcquireSharedInterruptibly入队列,自旋,阻塞,响应中断
-
四、共享模式可超时获取锁
-
- doAcquireSharedNanos入队列,自旋,阻塞,自动唤醒,响应中断
-
五、共享模式释放锁
-
- doReleaseShared唤醒后继
-
六、总结
何为共享锁?共享锁就是多个线程可以共享一把锁,如ReentrantReadWriteLock
的ReadLock
是共享锁,Semaphore
是共享锁,CountDownLatch
是共享锁,且这三个都是基于AQS
实现的。
在AQS中共享锁和独占锁一样,也实现了一套通用的模板,子类只需要实现如何获取锁(tryAcquireShared
),如何释放锁(tryReleaseShared
)的逻辑。
共享模式获取锁的过程和独占锁非常类似,都是先获取锁,失败之后进入同步队列操作。tryAcquireShared
在AQS中没有给出具体实现,在子类ReentrantReadWriteLock.ReadLock
、Semaphore
和CountDownLatch
中相应实现了获取共享锁的逻辑。
tryAcquireShared
返回值小于0,表示获取共享锁失败,从而进入同步队列操作doAcquireShared
。
public final void acquireShared(int arg) {
//tryAcquireShared 返回-1获取锁失败,返回值大于1或者0获取锁成功
if (tryAcquireShared(arg) < 0)
//获取锁失败,进入队列操作
doAcquireShared(arg);
}
1、doAcquireShared入队列,自旋,阻塞
doAcquireShared
的逻辑和独占锁acquireQueued
非常类似,入队列、自旋、阻塞等。基本流程如下:
-
新建共享节点node,并入队列(
addWaiter(Node.SHARED)
)。 -
自旋判断新节点node前驱是否是head,是则获取锁(
tryAcquireShared
),获取锁成功并传播唤醒后继(setHeadAndPropagate
)。 -
新节点node前驱不是head,则判断是否应该阻塞
shouldParkAfterFailedAcquire
。 -
判断应该阻塞,则阻塞当前线程
parkAndCheckInterrupt
。 -
被其他线程唤醒,或中断导致唤醒,则自旋重复2、3、4、5。
private void doAcquireShared(int arg) {
//创建一个读节点,并入队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;😉 {
final Node p = node.predecessor();
if (p == head) {
//如果前继节点是head,则尝试获取锁
int r = tryAcquireShared(arg);
if (r >= 0) {
//获取锁成功,设置新head和共享传播(唤醒下一个共享节点)
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
/**
-
p不是头结点 or 获取锁失败,判断是否应该被阻塞
-
前继节点的ws = SIGNAL 时应该被阻塞
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
(1)setHeadAndPropagate传播唤醒后继
自旋判断新节点node前驱是head,且尝试获取共享锁成功,则需要传播唤醒共享后继节点。基本流程如下:
-
node出队列,并成为新的head。
-
判断是否满足传播条件。
-
如若node还有后继且后继是共享节点,则执行唤醒操作
doReleaseShared
。
(共享锁的传播模式比较复杂,如需深入了解,请阅读拙作《AQS源码解读(七)——从PROPAGATE和setHeadAndPropagate()分析共享锁的传播性》)
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//设置node为新head
setHead(node);
//一连串判断满足后,唤醒共享后继
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
//唤醒后继共享节点
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
(2)判断是否应该阻塞和阻塞线程
共享锁判断判断是否应该阻塞和阻塞线程的代码和独占锁的完全一样,不过多赘述。shouldParkAfterFailedAcquire
主要做了如下几步:
-
判断node前驱状态是否为
SIGNAL
,是则直接返回true。 -
node前驱状态不是
SIGNAL
,有可能是ws>0
,说明前驱取消了,自旋跳过取消的节点,并寻找链接一个正常的前驱。 -
node前驱状态不是
SIGNAL
,有可能是0(初始化状态)或PROPAGATE
(传播状态),修改node前驱状态为SIGNAL
。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
-
This node has already set status asking a release
-
to signal it, so it can safely park.
-
node拿锁失败,前继节点的状态是SIGNAL,node节点可以放心的阻塞,
-
因为下次会被唤醒
*/
return true;
if (ws > 0) {
/*
-
Predecessor was cancelled. Skip over predecessors and
-
indicate retry.
-
pred节点被取消了,跳过pred
*/
do {
//pred = pred.prev;
//node.pred = pred;
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//跳过取消节点,给node找一个正常的前驱,然后再循环一次
pred.next = node;
} else {
/* 0 -3
-
waitStatus must be 0 or PROPAGATE. Indicate that we
-
need a signal, but don’t park yet. Caller will need to
-
retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
shouldParkAfterFailedAcquire
判断返回true,则调用parkAndCheckInterrupt
阻塞当前线程。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
acquireSharedInterruptibly
在代码结构上与doAcquireShared
类似,不同之处在于doAcquireShared
不响应中断,acquireSharedInterruptibly
响应中断。
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
//被打断 抛出异常
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
//获取锁失败
doAcquireSharedInterruptibly(arg);
}
doAcquireSharedInterruptibly入队列,自旋,阻塞,响应中断
doAcquireSharedInterruptibly
逻辑上也和doAcquireShared
类似,但是doAcquireSharedInterruptibly
在线程阻塞期间,被中断导致唤醒,将抛出异常InterruptedException
,响应中断,此时failed=true
,finally
块中会执行取消节点的操作,取消过程和独占锁一样。
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//新建节点入队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;😉 {
//node节点的前驱节点是head,获取锁
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//在阻塞期间因为中断而唤醒,抛异常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
和独占锁的可超时获取锁类似,共享模式超时获取锁,不仅可以响应中断,还可以将线程阻塞一段时间,自动唤醒。tryAcquireSharedNanos
可传入一个纳秒单位的时间nanosTimeout
,可超时的逻辑在doAcquireSharedNanos
中。
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
doAcquireSharedNanos入队列,自旋,阻塞,自动唤醒,响应中断
doAcquireSharedNanos
和doAcquireShared
逻辑类似,但是不仅可以响应中断,同时还可以让线程阻塞一段时间自动唤醒,如果超时了还没获取锁则返回false。
doAcquireSharedNanos
还有一个非常不同之处,就是即使shouldParkAfterFailedAcquire
判断应该阻塞了,也有可能不阻塞,还会再自旋一段时间,这个自旋的时长有一个阈值spinForTimeoutThreshold = 1000L
,1000纳秒,自旋了1000纳秒后还没有获取锁,且此时也判断应该阻塞了,就让线程休眠一段时间。
线程唤醒,有可能是自动唤醒,有可能是被其他释放锁的线程唤醒,还有可能是线程中断导致唤醒,如果是线程中断唤醒,则需要响应中断,抛出异常InterruptedException
;如果没有中断,则继续循环刚才的流程(判断前驱是否是head,判断是否超时,判断是否需要阻塞等)。
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
Kafka进阶篇知识点
Kafka高级篇知识点
44个Kafka知识点(基础+进阶+高级)解析如下
由于篇幅有限,小编已将上面介绍的**《Kafka源码解析与实战》、Kafka面试专题解析、复习学习必备44个Kafka知识点(基础+进阶+高级)都整理成册,全部都是PDF文档**
te boolean doAcquireSharedNanos(int arg, long nanosTimeout)
Kafka进阶篇知识点
[外链图片转存中…(img-eeb31wZd-1715811259510)]
Kafka高级篇知识点
[外链图片转存中…(img-AfcQ9eJJ-1715811259510)]
44个Kafka知识点(基础+进阶+高级)解析如下
[外链图片转存中…(img-JUVd5rz4-1715811259511)]
由于篇幅有限,小编已将上面介绍的**《Kafka源码解析与实战》、Kafka面试专题解析、复习学习必备44个Kafka知识点(基础+进阶+高级)都整理成册,全部都是PDF文档**