ReentrantLock实现原理

530 阅读4分钟

在上篇文章中我们结合源码介绍了AQS的数据结构及实现原理,在今天的文章中我们介绍下ReentrantLock的实现原理,也看看在ReentrantLock中是如何使用AQS的。

本篇文章的内容,依赖于上篇文章,大家需要先了解下AQS的源码,文章连接为:AQS源码

ReentrantLock是一个Java层面的锁的实现,也是我们常用的一个锁对象,是一个可重入的排他锁。

1 类结构

ReentrantLockLock的一个实现类,实现了Lock中定义的锁应该具备的功能。其类图如下:

image-20210604160026690

通过源码我们看到,在ReentrantLock中只有一个Sync类型的变量sync,锁功能便是通过改类及其子类实现的,Sync的类图如下:

image-20210604160245987

通过类图我们发现了上篇文章中介绍的AQS的身影,该类继承自AQS,并有两个实现类FairSyncNonfairSync,这两个类分别是ReentrantLock的公平和非公平实现。

2 lock和unlock

锁最主要的作用就是加锁和释放锁。在这部分我们会根据源码了解下其实现原理。

ReentrantLock.Sync中的方法如下:

image-20210607174203926

在上篇文章中我们知道AQS中有些方法是需要子类进行实现的,在上图中我们可以看到其实现了tryAcquiretryRelease两个方法。

接下来我们分别看下ReentrantLock中的公平锁和非公平锁都是如何处理的。

2.1 获取非公平锁

NonfairSync中的lock方法源码如下:

final void lock() {
    // 通过CAS操作修改state值
    if (compareAndSetState(0, 1))
        // 抢占锁成功 设置占用独占锁的线程为当前线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 抢占锁失败调用该方法 AQS中的方法   在该方法中会调用tryAcquire方法
        acquire(1);
}

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取state值
    int c = getState();
    // state是0代表为无锁状态
    if (c == 0) {
        // CAS操作替换state
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 当前线程已经获得锁  代表重入
    else if (current == getExclusiveOwnerThread()) {
        // 增加state值
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 修改state值   这里无需使用CAS操作
        setState(nextc);
        return true;
    }
    return false;
}

ReentrantLock的非公平锁的获取逻辑还是比较简单的,调用lock方法时先进行抢占锁操作,抢占失败了再加入阻塞队列。

2.2 获取公平锁

FairSync中的lock方法源码如下:

final void lock() {
    // 直接调用AQS中的acquire方法
    acquire(1);
}

protected final boolean tryAcquire(int acquires) {
    // 当前线程
    final Thread current = Thread.currentThread();
    // 当前状态值
    int c = getState();
    // state为0代表无锁状态
    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;
}

公平锁的加锁逻辑也是比较简单的,当阻塞队列中没有其他等待线程时才进行抢占锁操作,这便是和非公平锁的不通之处。

2.3 锁的释放

ReentrantLock.Sync中的tryRelease方法源码如下:

protected final boolean tryRelease(int releases) {
    // state值减去需要释放的锁的数量
    int c = getState() - releases;
    // 当前线程非获得独占锁的线程  抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 是否全部释放
    boolean free = false;
    if (c == 0) {
        free = true;
        // 设置独占锁的占有线程为null
        setExclusiveOwnerThread(null);
    }
    // 修改state值
    setState(c);
    // 是否释放掉  释放成功会走AQS中唤醒阻塞队列下个节点的逻辑
    return free;
}

至此ReentrantLock中锁的获取和释放逻辑,我们便讲解完了,逻辑还是比较简单的,建议大家在看源码时再点到AQS中看看,加深下对AQS的理解。

ReentrantLock的锁状态是通过state属性进行标记的,其取值如下:

  • 0 无锁
  • 大于0 线程获取锁的次数 重入时累加其值

获取锁和释放锁时对state进行加减操作,减到0时代表释放锁成功,唤醒AQS阻塞队列中的下个节点进行锁竞争。

公平锁和非公平锁的区别时,阻塞队列中存在其他阻塞线程,公平锁就插入阻塞队列尾部,非公平锁是在进行添加操作时都会进行一次锁竞争。

3 总结

在今天的文章中介绍了,ReentrantLock是如何在AQS的基础上实现的锁的功能,没什么复杂的逻辑。大家可以结合着AQS的源码一起来看看。

今天的文章就到这里了,在下次的文章中会介绍CountDownLatch同步工具的原理,大家可以先看看,其也是通过AQS实现的,逻辑也不难。

如果感觉对您有帮助,欢迎关注下公众号,您的关注是我更新的最大动力~

qrcode_for_gh_8febd60b14c9_258.jpg