上个文章加锁的问题
- 为什么t2要初始化一个无内容的Head?
- 为什么t3要修改t2的waitStatus=-1?
- 可重入锁怎么解锁的?
- Node存线程有什么用?
带着问题阅读源码
以t1unlock
为例
unlock
public void unlock() {
sync.release(1);
}
release
public final boolean release(int arg) {
//1.tryRelease 尝试释放锁
if (tryRelease(arg)) {
Node h = head;
//2.释放锁成功之后,有两个判断
//3.head = null 如果没有等待队列,head就为null,不为空说明有等待队列
//4.h.waitStatus !=0 waitStatus !=0 那就是等于-1。
//需要了解加锁过程,举个列子,t2会将head的 waitStatus 修改为-1,然后进入park
//如果 h.waitStatus = -1 说明 h的下一节点修改的,说明h的下一节点在park,需要unpark
if (h != null && h.waitStatus != 0)
//5.满足条件之后,走 unparkSuccessor去upark下一个节点
unparkSuccessor(h);
return true;
}
return false;
}
总结 release
需要细分为三个步骤 1.tryRelease
去尝试释放锁 2.释放锁成功之后,h != null && h.waitStatus != 0
判断是否需要去唤醒等待的线程 3.unparkSuccessor
去unpark线程
tryRelease
protected final boolean tryRelease(int releases) {
//获取state-1去解锁
int c = getState() - releases;
//当前线程等于持有锁的线程才能释放锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//判断state是否等于0,什么情况不会等于0,当重入锁的时候
//如果不是重入锁,就直接释放锁,设置当前持有锁线程=null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//是重入锁,重新设置state,锁减一
setState(c);
return free;
}
总结:tryRelease
1.通过cas修改state
来释放锁 2.如果是重入锁只是修改state 3.如果非重入锁,修改当前持有锁线程=null 4.释放锁成功返回true否则返回false
unparkSuccessor
private void unparkSuccessor(Node node) {
//此时 node = head
//因为t2会将head也就是上一个节点的 waitStatus 修改为-1
int ws = node.waitStatus;
//小于0就是 -1
if (ws < 0)
//通过cas修改node也就是head的 WaitStatus = 0 恢复成最开始
compareAndSetWaitStatus(node, ws, 0);
//获取下一个节点
Node s = node.next;
//unLock已经判断完了 s.waitStatus = -1 才会进来
//为什么这里还需要判断 s.waitStatus>0,因为如果线程t2被打断了,t2的waitStatus可能变成大于0
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)
//调用upark方法唤醒线程,此时通过node.thread获取到t2的线程
LockSupport.unpark(s.thread);
}
总结:unparkSuccessor
1.就是在重复判断下需要唤醒的node
的waitStatus
是否等于-1,防止线程被打断 2.调用unpark
方法唤醒线程 3.到这里为止就算是t1释放锁,唤醒t2
唤醒线程之后从哪里开始执行
从哪里park
就从哪里unpark
,所以需要了解加锁的过程。上一篇文件有涉及到加锁
tech.souyunku.com5f14f3…
被唤醒的线程从 acquireQueued
开始执行
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//unpark之后执行死循环到这里
//拿到t2上一个节点,也就是head
final Node p = node.predecessor();
//判断是否头节点,如果是尝试加锁,走加锁逻辑 tryAcquire
//这里t2加锁成功
if (p == head && tryAcquire(arg)) {
//设置t2为头节点,同时将 thread = null,因为没必要在持有线程了
setHead(node);
//将head的next置为null
p.next = null; // help GC
failed = false;
return interrupted;
}
//加锁到这里park之后,unpark同样也是从这里开始执行
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
总结:acquireQueued
1.被unpark
唤醒线程之后从acquireQueued
开始执行 2.先判断被唤醒的node
的上一个节点是否头节点 3.如果满足2,就调用tryAcquire
去尝试获取锁 4.被唤醒的node
获取锁之后设置头节点为当前node
5.获取锁之后就正常执行业务逻辑了
到这里算是把解锁说清楚了
解决问题
- 为什么t2要初始化一个无内容的Head?在每次释放锁的时候都会去拿
head
来判断是否需要解锁,所以t2需要虚拟出head
出来,在releas
释放锁中有通过head
来判断 - 为什么t3要修改t2的waitStatus=-1?同样在
releas
释放锁的时候会去判断head.waitStatus
是否-1,如果-1才能走解锁逻辑 - 可重入锁怎么解锁的?可重入锁解锁是将通过
cas
将state
-1 - Node存线程有什么用?
node
存线程,在调用unprak的时候可以传入需要unpark的线程