锁专题(3)ReentrantLock 可重入锁源码解析
2020年10月17日大约 9 分钟
点赞再看,已成习惯。

ReentrantLock 源码
介绍 ReentrantLock 的文章很多,今天我们来一起看一下 ReentrantLock 的源码,理解一下实现原理。
类定义
ReentrantLock 实现了 Lock 接口,和序列化接口。
/**
* @author 老马啸西风
*/
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
}
Sync 介绍
Sync 作为可重入锁的私有变量,实际上是继承自 AQS 的一个锁实现。
关于部分方法的解释我写在了注释中,便于大家理解。
ps:AQS 我们本节不做展开,后续会专门有一节进行讲解。
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
* @author 老马啸西风
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*
* 非公平获取锁。
* (1)如果当前没有线程持有锁,则通过 CAS 比较,如果 state=0,且设置为 acquires 成功,则设置持有锁的线程为当前线程。
* (2)如果持有锁的线程已经是当前线程,则设置 state += acquires;
* 这里还很细心,做了一个防止 overflow 的处理。
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc 即使将此锁设置为使用公平的排序策略,对 tryLock() 的调用也会立即获取该锁(如果有),无论当前是否有其他线程在等待该锁。即使破坏公平性,这种插入行为在某些情况下也可能有用。
个人的理解是这里的尝试获取锁就只有一次,虽然破坏了公平性呢,但是性能很好,常常是更符合使用者预期的。
ps: 这里不得不感慨一下,有些东西不看源码,是想不到的。
- 我们想使用公平模式获取锁怎么办?
[公平](https://p1.pstatp.com/origin/pgc-image/4d86a8d763934ce1962965c80dca89e8)
那问题又来了,我们想使用公平锁模式获取锁怎么办?
我们可以使用 `tryLock(0, TimeUnit.SECONDS)` 这个方法公平的获取锁。
### tryLock(long timeout, TimeUnit unit)
指定超时时间的获取锁方法,系统会统一转换为 nanos 去处理。
```java
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
- 纳秒是多久?
ns(nanosecond):纳秒,时间单位。一秒的十亿分之一,等于10的负9次方秒(1 ns = 10 s)。
光在真空中一纳秒仅传播0.3米。个人电脑的微处理器执行一道指令(如将两数相加)约需2至4纳秒。
- tryAcquireNanos
/**
* @author 老马啸西风
*/
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
可以看出这个方法同样支持中断。
/**
* Acquires in exclusive timed mode.
*
* @param arg the acquire argument
* @param nanosTimeout max wait time
* @return {@code true} if acquired
* @author 老马啸西风
*/
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
方法执行的时候设置了一个 deadline,有一句话怎么说来着。
deadline 是第一生产力,可能对于锁而言也是如此吧。
unlock()
释放锁。
public void unlock() {
sync.release(1);
}
- release()
这里会调用 tryRelease,如果成功还会有一些 Node 信息的更新。
/**
* @author 老马啸西风
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
其他还有一些方法,我们平时也不常用,此处就不做重点解析。
小结
锁作为我们高性能并发中并发安全的基础,是每一位 java 程序员的必备技能。
源码相对各种博客中的使用,显得有些无趣,但是有些东西,是源码中才有的知识。
相信你对技术有着更深的追求,极客的学习就样追根究底且枯燥。
本节中多次出现了一个类叫 AQS,下一节让我们一起来揭开这个类的神秘面纱。
各位极客的点赞转发关注,是我创作的最大动力,感谢你的支持!
参考资料
jdk8 源码
贡献者
binbin.hou