ReentrantLock源码分析(下)
本篇我们主要分析下ReentrantLock的unlock机制,首先看下unlock源码:
public void unlock() {
sync.release(1);
}
这里直接使用了AbstractQueuedSynchronizer的release方法,试图释放此锁。 如果当前线程是此锁所有者,则将保持计数减 1。如果保持计数现在为 0,则释放该锁。如果当前线程不是此锁的持有者,则抛出 IllegalMonitorStateException。我们继续跟踪下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释放锁成功,返回true。则调用unparkSuccessor函数来将头结点的继任节点唤醒。
接下来我们来具体看tryRelease(int arg)和 unparkSuccessor(Node node).
1. tryRelease(int arg)
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;
}
(1)先检查当前线程是不是拥有锁的独占线程。如果不是,则抛异常。这是因为线程不能够释放其它线程所拥有的锁。如果是,则进行 2.
(2)将AbstractQueuedSynchronizer状态位减少要释放的次数(对于独占锁次数为1)。如果剩余的状态位为 0(也就是没有线程持有锁).那么当前线程是最后一个持有锁的线程,调用setExclusiveOwnerThread来设置AbstractQueuedSynchronizer持有锁的独占线程为null.接着进行 (3)
(3)将剩余的状态位写会AbstractQueuedSynchronizer,如果没有线程持有锁,则返回true,否则返回false
这里c==0决定了是否完全释放了锁。由于ReentrantLock是可重入锁,因此同一个线程可能多重持有锁,那么当且仅当最后一个持有锁的线程释放锁是才能将AbstractQueuedSynchronizer中持有锁的独占线程清空,这样接下来的操作才需要唤醒下一个需要锁的AbstractQueuedSynchronizer节点(Node),否则就只是减少锁持有的计数器,并不能改变其他操作。
当tryRelease操作成功后(也就是完全释放了锁),release操作才能检查是否需要唤醒下一个继任节点。这里的前提是AbstractQueuedSynchronizer队列的头结点需要锁(waitStatus!=0),如果头结点需要锁,就开始检测下一个继任节点是否需要锁操作。
我们在上篇博文中知道acquireQueued操作完成后(拿到了锁),会将当前持有锁的节点设为头结点,所以一旦头结点释放锁,那么就需要寻找头结点的下一个需要锁的继任节点,并唤醒它。
2. unparkSuccessor(Node node)方法
源码如:
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
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);
}
上面代码的思路比较简单。就是找出头结点的下一个需要锁的继任节点然后唤醒。