Java并发编程之ReentrantLock源码(三)

32 阅读2分钟

前言

前面两篇文章Java并发编程之ReentrantLock源码(一)Java并发编程之ReentrantLock源码(二)介绍了ReentrantLock加锁成功和失败的流程,这篇文章主要介绍释放锁的流程。

释放锁流程

尝试释放锁
// ReentrantLock类
public void unlock() {
    sync.release(1);
}
// AQS类
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

解锁调用的是unlock方法,内部调用了AQSrelease方法,这里主要分为两部分来介绍:if条件语句和if语句块内部逻辑。

首先看下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;
}

首先获取state的值然后减1得到c,如果当前线程不是锁的拥有者那就没有资格来进行解锁,所以就直接抛出了异常;如果c0,表示释放了锁,把线程拥有者设为null,然后修改state的值直接返回true;如果c不等于0,表示有重入的情况,所以没有完全释放锁,只是减少了一次重入,然后修改state的值返回false

唤醒后继节点

回到上面release方法,如果解锁失败就直接返回false结束操作;如果解锁成功了,head节点如果不为null而且waitStatus不等于0,表示可以唤醒后继节点,唤醒方法unparkSuccessor源码如下:

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    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);
}

这里传入的参数是head节点,首先获取head节点的waitStatus,如果小于0的话就尝试修改成0,因为唤醒后继节点之后这个节点就没有用了。

中间部分的if代码块的意思是:获取head节点的后继节点准备唤醒它,但是如果这个节点是null或者被取消了,那就需要继续往后找到一个合适的节点来唤醒,但是因为这个后继节点已经无效了,所以没办法通过它继续往后找,所以这里采用的是从尾部往前找的方法,最终找到一个离head最近的节点。

最后判断如果这个节点不为null,就唤醒这个节点的线程,唤醒之后这个线程就按照上一篇文章Java并发编程之ReentrantLock源码(二)中介绍的逻辑继续尝试获取锁,这时就会成功了。