(本文源代码来自openjdk1.8,有错误和不足欢迎讨论指正)
看了前面讲述lock工作原理的文章,我们对ReentrantLock的实现方式有了了解。ReentrantLock中由其内部对象sync中的state成员记录其锁状态,当state=0时,代表无线程占用锁,state=n,n代表同一线程对锁的重入次数。lock的逻辑无疑就是尝试将state值从零改为1(占用)或者增加1(重入),否则排队阻塞至实现此工作。
那么我们不难推断unlock方法的逻辑,unlock方法不必考虑线程竞争的问题(因为ReentrantLock是排它锁,同一时间只有一个线程占有锁,因此只能有一个线程unlock),unlock应当会将state值减1,如果state值归0了,代表此线程已经完全释放了该锁,此时唤醒其后继节点参与竞争锁。查看其源代码,逻辑也确实如此。
当我们离开同步代码块的时候,需要释放掉持有的锁,往往在finally块中执行此逻辑以保证代码无论以何种方式结束都能释放锁。同样的,当我们调用unlcok方法时,ReentrantLock也是将此操作代理给了内部对象sync:
public void unlock() {
sync.release(1);//释放一个锁次数(因为可重入)
}
同样的,release进行释放锁的逻辑,他先调用tryRelease,可以看到tryRelease方法的返回值影响到了是否唤醒后继节点,因此在此我们可以推测tryRelease方法应当在完全释放锁的时候返回true:
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的实现如下,确是如同推测,当c==0时,返回true,此时锁被完全释放,进行唤醒后继节点操作:
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;
}
回到release方法,如果tryRelease返回true,那么进行后继节点的唤醒,执行unparkSuccessor,可能会有一个疑问是:非公平的锁抢占成功时,那么它的后继节点怎么是谁呢?其实,非公平的线程并没有修改任何队列信息,也就是没有入队,因此此时的队列头仍然是上一个释放锁的线程,这时候非公平的锁相当于唤醒了被它抢了机会的那个倒霉蛋。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitState(node, ws, 0);//后继节点被唤醒,signal置为0
Node s = node.next;//查找到后继节点
if (s == null || s.waitStatus > 0) {//如果当前没有后继节点后者后继节点任务已经取消
s = null;
//从尾巴开始寻找,找到最靠前的非取消节点
//不能从当前后继节点开始寻找,因为当一个节点被取消时,可能有其他线程已经把它的next置为了null,或者指向一个无用值以help gc。比如在cancelAcquire中有这样一段
//node.next=node.被取消的node的next引用指向了自己.
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)//找到了合适的节点,为它发放许可
LockSupport.unpark(s.thread);
}
这样队列头的后继线程就被发放了许可,可以进行锁的竞争了。