今天我们继续更新Java并发系列,往期内容大家可以点击Java并发系列查看。接着上一次内容,这次主要会讲解一下ReentrantLock的解锁流程.
unlock函数
上次我们提到了ReentrantLock的使用范式中:
class X {
private final ReentrantLock lock = new ReentrantLock();
//..
public void m() {
lock.lock();
try {
//.. method body
} finally {
lock.unlock();
}
}
}
锁使用完毕之后,需要调用lock.unlock()释放锁。在ReentrantLock的源码中,调用这个函数实际上:
public void unlock() {
sync.release(1);
}
函数直接调用了AQS的release函数。
AQS的独占式release函数
release的主函数
代码上比较简单:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
代码首先会调用自定义的tryRelease函数。如果release成功,程序会获取头节点。这时候头节点不为空,且头节点的状态不为0,程序就会将头节点进行unpark操作。在上一篇中,我们提到过,一个问题:
线程park之后,有谁来唤醒
加锁的时候,如果线程多次尝试都没获取锁成功,程序会把这个线程挂起(park)。这里也就是对应的unpark过程。
unparkSuccessor函数
private void unparkSuccessor(Node node) {
// 如果这个时候waitStatus是负数,对应的多半是等待信号,尝试将状态设置为0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// 如果后置节点是空,或者后置节点状态大于0(即为cancelled的),那么需要去掉无用节点,并连接队列
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);
}
这边会有一个比较有意思需要注意的点。平时队列更新都是从队尾开始,但是检查CANCELLED节点,反而是从队头开始。
这个原因需要回顾一下之前的addWaiter代码。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// P1: 这段逻辑也比较类似,如果当前队列不是空,那么无脑尝试一下是不是可以加入队列
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
node.prev = pred; 与 pred.next = node;负责将尾节点更新。由于这段代码分成2部分执行,那么有可能在执行unpardSuccessor的时候,有人正在并发加入尾节点。这个时候,就无法从尾部开始搜索节点了。
这些代码全部执行完毕,那么之前挂起代码就会重新开始执行。
ReentrantLock自定义的tryRelease函数
刚刚提到了,AQS的release函数会调用自定义的tryRelease函数。我们今天最后就回看一下ReentrantLock是如何实现对应的tryRelease函数:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
- 函数会获取当前的状态
- 查看当前申请解锁的Thread是否是之前加锁的Thread,如果不相等就抛错退出
- 由于ReentrantLock是独占锁,但是同一个锁可以多次重复上锁,所以对之前的状态不断递减。这个时候c == 0意味着当前线程所加的锁都去掉,free就能等于true
- 如果解锁成功,就去除所有状态,并且返回true
总结
最近比较忙,这个系列只能慢慢更新了。今天内容也比较少,只是稍微讲解了一下ReentrantLock的解锁过程。之后会慢慢把对应的Condition和waitStatus的内容补上。