今天我们继续更新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;
}
  1. 函数会获取当前的状态
  2. 查看当前申请解锁的Thread是否是之前加锁的Thread,如果不相等就抛错退出
  3. 由于ReentrantLock是独占锁,但是同一个锁可以多次重复上锁,所以对之前的状态不断递减。这个时候c == 0意味着当前线程所加的锁都去掉,free就能等于true
  4. 如果解锁成功,就去除所有状态,并且返回true

总结

最近比较忙,这个系列只能慢慢更新了。今天内容也比较少,只是稍微讲解了一下ReentrantLock的解锁过程。之后会慢慢把对应的Condition和waitStatus的内容补上。

Leave a Reply

Your email address will not be published. Required fields are marked *