ReentrantLock源码解析

阅读须知

  • JDK版本:1.8
  • 文章中使用/**/注释的方法会做深入分析

正文

ReentrantLock从命令上理解为可重入锁,提供了比synchronized更加灵活的锁控制,ReentrantLock基于AQS(AbstractQueuedSynchronizer,不熟悉的读者可以查阅笔者关于AQS源码分析的相关文章进行学习,便于更好的理解本文)实现,首先我们来看一下ReentrantLock的构造方法:
ReentrantLock:

public ReentrantLock(boolean fair) {
    //根据传入的boolean变量fair来确定使用公平锁会非公平锁
    sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock默认的无参构造方法使用的是非公平锁,我们就先来看一下非公平锁的加锁实现:
ReentrantLock.NonfairSync:

final void lock() {
    //CAS将AQS的state变量从0改为1,成功即为获得锁
    if (compareAndSetState(0, 1))
        //成功则设置独占锁的拥有者线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        //如果CAS失败,证明锁已经被占用,则调用AQS的acquire方法来进行锁重入的判断、再次尝试获取锁、入队阻塞等待等操作
        acquire(1);
}

AQS的acquire方法我们在AQS(AbstractQueuedSynchronizer)源码解析(独占锁部分)这篇文章中进行过详细的分析。方法的第一步就是调用由子类实现的tryAcquire方法通过操作state变量尝试以独占模式获取锁,这里可能会有疑问,上面的CAS操作失败了不就说明锁已经被占用了么,不是应该直接入队阻塞等待锁释放时被唤醒么,这里为什么还会再次尝试获取锁呢?因为到这里可能锁已经被释放,也有可能是同一个线程占用的锁,这样我们可以实现锁重入功能,只有这些条件都不满足才会进行入队阻塞等待操作。我们来分析tryAcquire方法的实现:
ReentrantLock.NonfairSync:

protected final boolean tryAcquire(int acquires) {
    /*尝试非公平独占模式获取锁*/
    return nonfairTryAcquire(acquires);
}

ReentrantLock.Sync:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState(); //获取state变量
    if (c == 0) {
        //如果state变量的值为0,说明锁已经被释放,则再次尝试CAS操作获取锁
        if (compareAndSetState(0, acquires)) {
            //成功则设置独占锁的拥有者线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //判断独占锁的拥有者线程是否是当前线程
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        //state变量是int型,这里如果nextc小于0说明已经超过int的最大值,抛出Error已经超过最大锁定计数
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        //state变量,同时也代表锁定计数
        setState(nextc);
        return true;
    }
    return false;
}

非公平锁的重入功能也就是在这里实现的了。下面我们来看公平锁的加锁实现:
ReentrantLock.FairSync:

final void lock() {
    acquire(1);
}

我们发现与非公平锁的lock方法的区别就在于非公平锁首先会尝试CAS修改AQS的state变量来尝试获取锁,失败了才会调用acquire方法,而公平锁直接调用acquire方法。我们继续分析公平锁对于tryAcquire方法的实现:
ReentrantLock.FairSync:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        //这里的判断多了一步,判断当前线程是否是下一个能够优先获得锁的线程
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //锁重入功能的实现与非公平锁的实现相同
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

我们发现,公平锁与非公平锁对tryAcquire方法的实现的唯一区别就是公平锁只有在当前线程是下一个能够优先获得锁的线程的情况下才会去尝试获取锁。hasQueuedPredecessors方法主要确认以下几种情况:

  • 等待队列为空
  • 当前线程所在的节点是头结点
  • 当前线程所在的节点是头结点的后继节点

到这里,我们已经可以分析出公平锁和非公平锁的区别的原理了:

  • 公平锁会严格的按照线程入队的顺序来获取锁,我们在上文的分析也看到了,公平锁的lock方法并不会直接尝试获取锁,只有在当前线程是下一个能够优先获得锁的线程的情况下才会尝试获取锁(这里我们抛开锁重入的情况),我们在AQS独占锁源码解析的文章中提到,AQS依赖FIFO(first-in-first-out 先进先出)等待队列来完成线程的排队工作,当hasQueuedPredecessors方法返回true时,说明当前线程不是下一个应该获取锁的线程,公平锁就是靠这样的控制来保证“公平”的。
  • 相反,非公平锁并不会严格的按照线程入队的顺序来获取锁,每一个新加入的线程都会首先尝试获取锁,只有锁获取失败时才会进行入队阻塞等待,所以它是“非公平”的。

最后我们来看锁释放的流程,公平锁和非公平锁的锁释放流程是一样的:
ReentrantLock:

public void unlock() {
    sync.release(1);
}

这里的release方法我们在AQS独占锁源码解析的文章中同样进行过详细的分析,AQS的release方法首先会尝试调用由子类实现的tryRelease方法来尝试设置state变量来释放独占锁,锁完全释放后,会对后继节点进行唤醒操作,这个流程我们已经分析过,不再赘述。我们来看ReentrantLock的对tryRelease方法的实现:
ReentrantLock.Sync:

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;
        //如果锁计数为0,说明锁已经被完全释放,则将锁的拥有者线程置为null
        setExclusiveOwnerThread(null);
    }
    setState(c); //设置当前state
    return free; //返回锁的是否空闲状态
}

这样锁释放的流程就分析完成了。

ReentrantLock还支持Condition,ReentrantLock的Condition完全基于AQS的ConditionObject实现,我们已经分析过ConditionObject源码,不明白的同学可以前往进行查阅学习。到这里,ReentrantLock的源码分析就完成了。

猜你喜欢

转载自blog.csdn.net/heroqiang/article/details/79781682