ReentrantLock源码解析
文章目录
- ReentrantLock源码解析
- 一、ReentrantLock
- 二、ReentrantLock 的 Sync、FairSync、NonfairSync
- 2.1 Sync、FairSync、NonfairSync
- 2.2 NonfairSync 下的 tryAcquire
- 2.3 FairSync下的 tryAcquire
- 2.4 tryRelease
- 三、lock.lock()
- 3.1 NonfairSync.lock()
- 3.2 FairSync.lock()
- 四、lock.unlock()
- 五、总结
一、ReentrantLock
ReentrantLock 是 Java JUC 中的一个可重入锁,ReentrantLock锁 是基于 AQS 实现的。
在 AQS 中,如果需要使用AQS的特征则需要子类根据使用的场景,重写下面方法:
//查询是否正在独占资源,condition会使用
boolean isHeldExclusively()
//独占模式,尝试获取资源,成功则返回true,失败则返回false
boolean tryAcquire(int arg)
//独占模式,尝试释放资源,成功则返回true,失败则返回false
boolean tryRelease(int arg)
//共享模式,尝试获取资源,如果返回负数表示失败,否则表示成功。
int tryAcquireShared(int arg)
//共享模式,尝试释放资源,成功则返回true,失败则返回false。
boolean tryReleaseShared(int arg)
由于这里 ReentrantLock 锁的特性,所以下面只需关注独占模式下的几个方法即可。
关于 AQS 中的方法解析可跳转查看 AQS源码解析 这篇文章
下面开始 ReentrantLock 源码的分析:
二、ReentrantLock 的 Sync、FairSync、NonfairSync
2.1 Sync、FairSync、NonfairSync
在声明 ReentrantLock 锁时,有两种方式,一种是无参构造函数,一种则需要指定一个 fair 参数:
new ReentrantLock();new ReentrantLock(false);
当使用无参构造函数声明时,则是创建了一个 NonfairSync 对象:

通过有参的构造函数,则根据传入的 fair 可以选择创建一个 FairSync 对象:

其实这里也不难理解 NonfairSync 和 FairSync 其实就是ReentrantLock 锁中的非公平锁和公平锁两种类型。
点到这两个类中,可以看到都继承自 Sync 类:

而 Sync 类,则继承了 AQS :

到这里,我们寻找几个关键的方法,在AQS中独占模式下,两大关键的方法是交由子类进行实现的,分别是 tryAcquire() 尝试获取资源,和 tryRelease() 尝试释放资源。
首先来看 tryAcquire() 尝试获取资源:
通过 Sync 类的实现源码发现并没有重写 tryAcquire() 方法,那该方法肯定在下面的子类FairSync 和 NonfairSync ,分别看下源码确实存在重写的方法:


2.2 NonfairSync 下的 tryAcquire
首先看下 NonfairSync 的 tryAcquire() 实现逻辑,可以看到又调用了 nonfairTryAcquire() 就是 Sync 类中的 nonfairTryAcquire() ,从命名上可以分析出就是非公平锁的尝试获取资源,直观就是非公平锁下获取锁操作:

进入到 Sync 类中的 nonfairTryAcquire()中,可以看到首先获取到 AQS 中的共享资源 state,如果 state 等于 0 ,则将 state 的值修改为 acquires(默认为1,下面会分析到),并设置AQS的独占线程为当前线程,并返回 true ,说白了不就是获取到锁了吗,那就可以理解为 state 等于 0 即是无锁的状态,下面将 state 的值修改为 acquires 就是获取到锁了,改变资源的状态:

接着如果 state 的值不是 0 ,则当前锁已经被别的线程持有了,这里又判断了下,如果持有锁的线程正好是当前的线程,那不就是锁的重入吗,这种情况下可以直接获得锁,不过这里为了记录重入的次数,对 state 共享资源进行了 + acquires 操作,其实就是 +1 操作。

如果都没有成功,那此时则获取锁失败,返回 false
2.3 FairSync下的 tryAcquire
在 FairSync 类下的 tryAcquire() 方法中,和前面 NonfairSync 类似,但不同的是,在获取到锁时,也就是拿到 state 等于 0,进行修改资源时,多了步 hasQueuedPredecessors() 的判断:

下面可以进到 hasQueuedPredecessors() 的方法中,可以看到是由 AQS 提供的方法,主要就是判断当前节点线程的前面是否还有等待的线程,因为 FairSync 实现的是公平锁的原则,如果当前线程前面还有等待线程,则获取锁资源也轮不到自个,让前面的老大先来:

hasQueuedPredecessors() 方法理解后,其余的逻辑则和 NonfairSync() 中的一致了。
2.4 tryRelease
到这里已经了解到了tryAcquire() 尝试获取资源的逻辑,上面提到了两个重要方法,还有一个 tryRelease() 没有分析逻辑,还是首先看 Sync 类中是否有重写该方法:
通过源码可以看到,在 Sync 类中就已经对 tryRelease() 进行了重写,而 NonfairSync 和 FairSync 中都没有重写该方法,那释放资源就是走的 Sync 类下的 tryRelease() 方法:

在该方法中,可以看到首先还是获取到了 AQS 中的 state 共享资源,然后对该资源进行 - releases (默认releases为1,下面会提到)操作,其实就是 -1 操作:

接着判断了下,如果当前线程不是持有锁线程,就抛出异常,也好理解,没有持有锁的线程跑过来释放锁,那肯定有问题了呀。
接着再进行判断 state 是不是等于 0 ,上面讲到在锁重入的情况下,记录重入的次数是对 state 进行 +1 操作,而这边又对 state 进行 -1 操作,如果减到最后 state 有成了最初的 0 ,那不就是重入的锁和当前持有的锁都释放完了吗,这个时候就可以将持有锁的线程置为空了,并修改最新的 state :

看到这里就会发现获取锁和释放锁,无非就是对 AQS 中的共享资源进行操作。理解了这两大核心的方法后,下面就可以看如何运用在 ReentrantLock 中的了。
三、lock.lock()
在 ReentrantLock 中,需要获取锁时,直接使用 lock.lock() 即可,那 lock.lock() 到底做了什么呢,点到该方法中,可以看到是调用的 Sync 的 lock() 方法,而 Sync 中的 lock() 方法是抽象方法,具体实现肯定在子类的 NonfairSync、 FairSync 中。

3.1 NonfairSync.lock()
首先点进 NonfairSync 非公平锁中的 lock() 方法,直接进行了将 AQS 中的共享资源 state 由 0 改为 1 ,如果修改成功,根据上面分析的结论不就是获取锁成功了吗,可以将 AQS中的独占线程设为自己了。但是如果其他线程修改成功了,这里使用 CAS 就会修改失败,因此就会进到 acquire() 方法,注意这里传递的参数默认就是 1 ,对应着前面括号中的说明:

而 acquire() 方法,就是 AQS 中的独占模式获取同步资源的逻辑,会调用当前方法的 tryAcquire() 尝试获取资源,如果获取不到,则加入到 AQS 的阻塞队列并阻塞挂起线程。

关于 acquire 方法的源码解析可查看 AQS源码解析 。
3.2 FairSync.lock()
在 FairSync 公平锁中,由于需要遵循先进先出的原则,这里没有直接已粗暴的形式对 state 进行修改,而是直接调用了 AQS 中的 acquire() 方法,而 acquire() 方法又会调用当前类的 tryAcquire() 获取资源。
但在当前类的 tryAcquire() 方法中,如果获取到了资源,会接着进行判断当前线程的前面是否还有等待的线程,如果有则让出来让别人获取资源,因此就遵循了公平锁的原则,注意这里传递的参数默认就是 1 ,同样对应着前面括号中的说明:

同样 tryAcquire() 尝试获取资源,如果获取不到,则加入到 AQS 的阻塞队列并阻塞挂起线程。
四、lock.unlock()
上面了解到了 lock() 的逻辑,既然上锁了肯定需要解锁,下面点到 unlock() 方法中,可以看到直接使用了 Sync 的 release() 方法释放资源,其实是 AQS 中的 release() 方法,注意这里传递的参数默认就是 1,同样对应着前面括号中的说明:

在 AQS 的 release() 方法中,首先会调用 Sync 类的 tryRelease() 释放资源,然后对已阻塞的线程进行唤醒:

关于 release() 方法的源码解析可查看 AQS源码解析 。
五、总结
通过阅读 ReentrantLock 的源码可以发现,大量依赖于 AQS 中提供的方法,所以在阅读前一定要理解下 AQS 的作用和功能。