ReentrantLock源码解析--解锁过程

228 阅读4分钟

对于ReentrantLock来说,无论公平锁还是非公平锁,它的解锁过程都是相同的,这里我已非公公平锁进行举例。

    ReentrantLock lock= new ReentrantLock () ;
    lock.lock();
    lock.unlock();

当调用lock.unlock时,解锁就开始。解锁的具体执行类同样也是Sync这个继承了AQS的内部类。

/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented.  If the hold count is now zero then the lock
* is released.  If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
*         hold this lock
*/
public void unlock() {
    sync.release(1);
}

注释分析

//尝试去释放锁
Attempts to release this lock.
//如果当前线程是此锁的保持器,则将计数器减少。
If the current thread is the holder of this lock then the hold count is decremented.
//如果保留计数现在为零,则锁定释放。
If the hold count is now zero then the lock is released.
//如果当前线程不是锁,抛出{@link IllegalMonitorStateException}。 
If the current thread is not the holder of this lock then {@link IllegalMonitorStateException} is thrown.

加锁过程中我们分析过,ReentrantLock中有个state变量又来记录当前锁的重入次数,如果state==0,说明未被线程持有,state如果大于0,则说明被重入了几次。通过注释我们可以知道,这个unlock方法执行一次就是减少一次重入(state-1),如果state变量等于0,那么就需要释放锁了。接下来我们来看它的具体实现。

/**
 * Releases in exclusive mode.  Implemented by unblocking one or
 * more threads if {@link #tryRelease} returns true.
 * This method can be used to implement method {@link Lock#unlock}.
 *
 * @param arg the release argument.  This value is conveyed to
 *        {@link #tryRelease} but is otherwise uninterpreted and
 *        can represent anything you like.
 * @return the value returned from {@link #tryRelease}
 */
public final boolean release(int arg) {
    //(1)
    if (tryRelease(arg)) {
        Node h = head;
        //(2)
        if (h != null && h.waitStatus != 0)
            (3)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

这里它调用AQS中的release()方法。从注释中我们可以知道tryRelease(arg)这个方法需要具体的实现类。与tryAcquire不同的是,tryAcquire交给了Sync的子类公平锁(FairSync)和非公平锁(NonFairSync)来分别实现,而tryRelease直接由Sync来实现,所以无论是公平锁还是非公平锁,解锁方式都是相同的。

在(1)调用了tryRelease()方法。

protected final boolean tryRelease(int releases) {
    //(1.1)
    int c = getState() - releases;
    //(1.2)
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //(1.3)
    if (c == 0) {
        free = true;
        //(1.4)
        setExclusiveOwnerThread(null);
    }
    //(1.5)
    setState(c);
    //(1.6)
    return free;
}
  • (1.1)将获取当前的state,并将state - releases的值赋值给c,releases的值为1,也就是将重入的次数减去一次。
  • (1.2)判断当前的线程是否是Thread.currentThread()是不是当前持有锁的那个线程,不是就抛出异常。很好理解,因为只有持有锁的线程才能执行解锁操作。
  • (1.3)这里判断c是否等于0也就是是否当前是否能执行解锁操作,如果解锁,则(1.4)将当前持有锁的线程设置为空。
  • (1.5)最后保存state状态
  • (1.6)只有解锁的情况才会返回true,如果只是单纯的减少重入次数更新state,依旧返回false。

如果tryRelease()返回了false,n那么release方法也就返回了false,解锁过程也就结束了。这种情况所并没有被释放。

如果tryRelease()返回了true,也就是state等于0,那么当前线程就释放了锁,接着执行if中的代码。这么部分的代码我们通过猜测也能知道应该是唤醒当前队列中的下一个线程。(在加锁过程中我们就知道不论是公平锁还是非公平锁,未获取到锁的线程就会进入队列排队。)

代码(2)h != null 判断对头是否为空,如果对头为空,说明说队列中没有正在等待锁的线程,也就没有必要唤醒线程了。 同理h.waitStatus != 0作用也是一样的。 waitStatus的状态我们在加锁时也分析过,初始状态为0,如果有线程在当前节点之后排队,那么会将排队线程节点的上一个节点状态置为-1。举个例子假设T1获取到锁,这时来了一个T2排队,那么就会将T1的状态置为-1,如果又来了T3,就会将T2的状态置为-1(T1,T2初始为0)。所以如果h.waitStatus != -1,也就是说当前节点后面有人在排队。这两种情况都要唤醒在排队的线程。

/**
 * Wakes up node's successor, if one exists.
 *
 * @param node the 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;
    //(3.1)
    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;
    //(3.2)
    if (s == null || s.waitStatus > 0) {
        s = null;
        //(3.3)
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

代码(3)就是唤醒线程的操作,这里将对头的node节点传入,如果(3.1)对头的waitStatus状态小于0,则将它置为0。 (3.2)ws > 0的情况是ws = 1被取消或者超时了(CANCELLED: This node is cancelled due to timeout or interrupt.Nodes never leave this state. In particular a thread with cancelled node never again blocks)。或者下个节点为空。 正常情况下s== null不成立,我们只要唤醒下个节点就行了。 (3.3)但是如果这个节点被取消或者为空,就需要从队尾往队头遍历,找到最靠前的那个节点。