持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情
独占锁模式 前提: t1线程获取到锁,t2线程未获取锁;
t2线程执行到指定代码
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);
}
}
```
private final boolean parkAndCheckInterrupt() {
// t2线程执行到此处 LockSupport.park(this);
return Thread.interrupted();
}
```
t2线程在执行LockSupport.park(this)时发生上下文切换(该方法还未执行);
t1线程释放锁,准备唤醒t2
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor方法;
(h);
return true;
}
return false;
}
由于head节点的waitStatus=-1满足唤醒条件;进入unparkSuccessor方法;
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
- t1线程首先将head节点的waitStatus通过cas设置为0;
- 从尾结点向前查找最后一个未取消的节点t2节点,将其唤醒;
- 但是t2线程并未执行LockSupport.park方法,此时无效唤醒;
- t2线程切换回来,开始执行LockSupport.park()方法,此时t2线程永远无法被唤醒,造成死锁;
意想不到
你以为上述就结束了,其实并没有,当我去翻看LockSupport.park的源码时发现:
LockSupport#park
LockSupport提供的是一个许可,如果存在许可,线程在调用park的时候,会立马返回,此时许可也会被消费掉,如果没有许可,则会阻塞;
LockSupport#unpark
为线程提供“许可(permit)”,线程调用park函数则等待“许可”。这个有点像信号量,但是这个“许可”是不能叠加的,“许可”是一次性的;
总结
上述的猜想前提: 获取锁的线程先unpark,未获取锁的线程再park时发生死锁;再结合源码分析这种情况不成立,所以不会发生死锁;