ReentrantLock 源码解析
创始人
2025-06-01 05:52:58

前言

注:本文的源码来自 JDK11

ReentrantLock 是 Java 中的一个可重入锁,它可以用于替代传统的 synchronized 关键字来进行线程同步。
下面是与 synchronized 关键字的一些对比:

名称实现重入性中断性公平性是否支持超时释放锁
ReentrantLockJava API级别,基于AQS实现可重入可中断支持非公平与公平可以超时手动是否锁
synchronizedJVM 级别,基于对象的 monitor 对象实现可重入不可中断非公平无法设置超时自动是否锁

可以看出 ReentrantLock 提供了更多的灵活性和可扩展性,不知你是否开始对它的原理产生兴趣?

注意:你需要对 AQS 的工作原理有所了解,因为 ReentrantLock 是在 AQS 的基础上实现的。

类结构

ReentrantLock 类实现了 java.util.concurrent.locks.Lock 接口,它的内部实现包括一个 Sync 内部类,该类是ReentrantLock 的核心实现。
Sync 继承了 AbstractQueuedSynchronizer 抽象类,用于管理线程的同步状态。Sync 类有两个子类,分别是NonfairSyncFairSync,用于实现非公平锁和公平锁。UML 如下图所示:

Reentrant lock uml

Sync 类源码

由于公平锁与非公平锁是 Sync 的子类,那么我们先分析 Sync

abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;/*** 默认的非公平实现,有个细节是,为什么非公平的实现要放父类这里?* 其实 ReentrantLock 的 tryLock() 就是直接调这里的,不管你是哪个实现,都是使用非公平的实现。*/@ReservedStackAccessfinal boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();// 如果 state 为 0 表示没有线程占用锁if (c == 0) {// cas 成功表示拿到锁if (compareAndSetState(0, acquires)) {// 设置当前获取锁的线程setExclusiveOwnerThread(current);return true;}}// 因为是可重入锁,当 state 不为 0 时,再判断下如果为当前线程,则将 state 加上去,释放锁的时候再减else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;// 从这里可以知道,可重入次数是有限制的,即为 2147483647if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");// 由于是自己线程,不可能存在竞争关系,没必要用 cassetState(nextc);return true;}return false;}/*** 释放锁的逻辑,非公平与公平的都是一样的,因为已经获取到锁,因此这里的代码都线程安全的,都不用 cas*/@ReservedStackAccessprotected 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;}// 略...
}

FairSync(公平锁源码)

加锁

下面先来看公平锁的加锁逻辑,这里只需实现 tryAcquire,因为 父类 Sync 已经实现了 tryRelease 方法。

static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;/*** 公平锁的 tryAcquire 方法,这里跟非公平的实现几乎一样,只不过多了 hasQueuedPredecessors() 的判断*/@ReservedStackAccessprotected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// hasQueuedPredecessors() 表示在当前线程之前,队列里是否还有线程等待// true:  head --> node(thread1) --> node(currenThread) // false: head --> node(currenThreaad)// 因为必须保证公平,所以只需要按照 AQS 的 FIFO 队列来就好了,当前线程没有在队头就获取不到锁返回 falseif (!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;}
}
解锁

解锁直接使用父类的,因此不用重写。

NonfairSync(非公平锁源码)

加锁

下面先来看非公平锁的加锁逻辑,非常的简洁。

static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;protected final boolean tryAcquire(int acquires) {// 直接使用父类的方法,在上面已经分析过了,请拉回去看看return nonfairTryAcquire(acquires);}
}
解锁

同样的解锁直接使用父类的,也不用重写。

为什么非公平锁的效率比公平锁的高?

很多时候都不推荐使用公平锁的方式去加锁,都说使用非公平锁的效率要比公平锁的要好,但是为什么呢?
从上面的源码来看,公平锁也仅仅是多了 hasQueuedPredecessors() 的判断,其实在唤醒队列中阻塞的线程时涉及到上下文切换,这需要一定的时间消耗,多个线程的耗时累积起来效率自然就低了。
要尽量地减少这个上下文切换的时间也很简单,直接让当前的线程去抢锁,因为当前线程就在用户态,不会有上下文切换这个耗时,效率自然就好了。

总结

本文对比了 ReentrantLocksynchronized 的区别,知道 ReentrantLock 在用法上更加的灵活;分析了ReentrantLock 公平锁与非公平锁的实现,发现代码是非常简单的,这都得益于 AQS 这个抽象同步框架,其实它们的主要区别是在加锁的时候会不会尝试获取锁;最后思考了为何非公平锁的效率会比公平锁的高。

相关内容

热门资讯

陕西省首届单板滑雪F级挑战赛在... 来源:环球网 第十二届全国大众冰雪季暨陕西省单板滑雪(F级)挑战赛12月27日在宝鸡鳌山滑雪度假区迎...
银兰高铁三年来累计发送旅客91... 中新网银川12月29日电 (记者 杨迪)2025年12月29日是银兰高铁贯通运营三周年,记者当天从中...
面向应届生,北京朝阳区多所学校... 转自:北京日报客户端北京市朝阳区教育委员会所属事业单位面向应届毕业生及出站博士后公开招聘公告按照北京...
迪普科技:投资者询问实控人承诺... 投资者提问:请问:2022 年浙江证监局责令郑树生继续履行转让承诺。实控人目前是怎么落实的?如何来完...
中密控股:投资者指其进取心不足... 投资者提问:董秘你好!看看公司股价和业绩,增长幅度连通胀都比不上,股价已经5年没涨了,可怜呀!关键是...