上篇讲解了ReentrantLock的加锁过程,本篇我们接着讲解ReentrantLock解锁过程的源码。
ReentrantLock解锁解析
我们还是先写一个应用,从unlock() 方法一步一步进行底层源码分析。
0.先写一个Demo
public static void main(String[] args) {
final ReentrantLock reentrantLock = new ReentrantLock(true);
Thread t1 = new Thread(() -> {
reentrantLock.lock();
sleep(2000);
reentrantLock.unlock();
});
t1.start();
}
1.首先调用模板方法sync.release(1)
由于公平锁和非公平锁的解锁过程一样,这里ReentrantLock都会调用sync.release(1) 方法。
public void unlock() {
sync.release(1);
}
2.模板方法 release(1) 分析
public final boolean release(int arg) {
// 尝试释放锁,如果成功则会调用unparkSuccessor(h) 下文解析
if (tryRelease(arg)) {
Node h = head;
// 如果队头不为空且后继线程存在某种状态,那么会唤醒第一个排队线程
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
3.tryRelease(arg) 尝试解锁方法分析
protected final boolean tryRelease(int releases) {
// 持有锁 state-1 ,判断之后是否为自由状态 (c=0?)
// 可能会是重入,那么 state-1,之后也不能释放锁
int c = getState() - releases;
// 当前解锁线程不是持有锁的线程,这种情况很少出现
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果 state 可以为自由状态,那么就让持有线程变为null
// 解锁成功,返回true
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 最后还要重新设置 state 的状态
setState(c);
return free;
}
4.unparkSuccessor(h) 唤醒队列等待线程方法分析
需要注意:上篇我们已经分析了前一个结点 ws 会保存后一个结点线程的状态
private void unparkSuccessor(Node node) {
// 这里获取队头结点的下一个结点线程的状态 ws
int ws = node.waitStatus;
// 如果 ws=-1(SIGNAL),那么就置为初始状态 ws=0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 获取队头结点的下一个线程结点,也就是第一个排队结点
Node s = node.next;
// 队列只有一个队头结点 或者 ws>0 s结点线程需要从同步队列中取消等待
if (s == null || s.waitStatus > 0) {
s = null;
// 从队列中队尾往前,找到距离head最近 ws<=0 的结点(线程状态正常)
// 如果遇到结点为null或者结点为队头,那么就结束循环
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 如果找到的这个结点不为null,那就唤醒它
if (s != null)
LockSupport.unpark(s.thread);
}
一般情况下会唤醒队头结点的下一个结点线程,但如果该结点线程处于ws=1状态,那么就从队尾开始向前找到距离队头最近的装填正常的线程结点并唤醒。(因为这里不能判断本结点的线程状态,所以只能找到 ws<=0的线程结点)
疑问:既然在判断时 s=null 直接返回不就行了,为什么还有进入循环查找结点?
这里的代码块并未原子性操作,能调用 unparkSuccessor() 方法说明持有锁的线程
已经解锁成功,那么在 unparkSuccessor() 方法判段 s == null 时队列中没有元素,
但由于是多线程,可能在进入if代码块之后,有多个线程来尝试lock,这时,队列元素
就会增加,即便在判断时s=null,但不能保证下一时刻还是s=null,所以进入下面循环
是必要的。
unlock() 流程图
参考:
1、《Java并发编程的艺术》
2、JUC AQS ReentrantLock源码分析(一)
3、ReentrantLock实现原理分析