ReentrantLock
(简称RLock) 是Java的一种锁机制。从API上看,RLock提供了公平锁与非公平锁,并提供了当前锁状态监测的一些接口。其内部是由 FairSync
与 NonFairSync
来实现锁资源的抢占与释放。下面咱们来学习下其源码。node
首先咱们打开 RLock 的构造函数,源码以下:c#
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
复制代码
好的,很直白,根据入参构形成员变量 Sync。默认为 NonfairSync
。到这里,咱们遇到了第一个新概念 Sync
。先按住好奇心,咱们先找到 lock()
和unlock()
源码。以下:bash
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
复制代码
到这里好像有些明白了,RLock更像是对Sync的进一层封装,经过多态来实现不一样的锁策略。到这里,我有个疑问,公平锁和非公平锁的策略有何不一样呢?那么这样看来,啃透Sync
是关键。并发
首先咱们捋一下 Sync
继承结构。IDEA里鼠标移到类声明上,Ctrl+H
便可清晰看到类的继承结构。 框架
AbstractQueuedSynchronizer
(简称AQS)。这个类是JDK并发包中的锁基类,定义了锁资源获取与释放的框架与基本行为。这个先略过不谈,继续贯彻第一步,从最直白的方法入手。
咱们回忆一下lock的行为,咱们调用来获取锁,若是其它线程已抢占到锁资源,当前线程挂起,直到当前线程获取到锁。并且RLock支持重入。 NonfairSync#lock()
与 FairSync#lock()
源码以下: 函数
NonfairSync
先设置了状态位,而后调用了
acquire()
。
FairSync
则直接调用了
acquire()
。那么咱们先从
compareAndSetState()
入手,源码以下:
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
复制代码
这个函数定义在 AQS 中,用于设置当前lock的状态,unsafe呢,是JDK中很是变态的一个工具类,能够直接操控实例对象所在的内存,同时提供了一些原子操做。具体的后面会再展开介绍,简而言之,这个函数能够理解为:工具
protected final boolean compareAndSetState(int expect, int update) {
synchronized (this.getClass()) {
if (this.state == expect) {
this.state = update;
return true;
} else {
return false;
}
}
}
复制代码
那么到这里咱们好像获得了第一把钥匙:lock.state 为0时,为空闲,而上锁请求会将状态置为1,而且将exclusiveOwnerThread
设为当前线程。学习
接下来,咱们来看 acquire()
。继续跟踪下去, 的 acquire()
代码以下:ui
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
唔,好像看不出来什么东西,那么继续跟下去,先从 tryAcquire 开始,NonfairSync
和 FairSync
tryAcquire() 核心代码以下 this
FairSync
获取锁以前会调用
hasQueuedPredecessors()
源码以下:
/**
* @return {@code true} if there is a queued thread preceding the
* current thread, and {@code false} if the current thread
* is at the head of the queue or the queue is empty
* 两种状况返回 false:1. 当前线程在队头。2. 队列为空
*/
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
复制代码
那么咱们拿到了第二把钥匙: 公平锁在获取锁以前会先去查询是否有其余人在等待这把锁,若是没有,再尝试获取。而非公平锁则不会询问
这么看来,公平锁 Peace&Love,像民谣,多愁善感,与世无争。而非公平锁 Aggressive,像Hip-Hop,张扬自我,锐意进取。 那么回到 acquire()
,还有一个函数: acquireQueued()
,源码以下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
忽略其余细节,咱们在 parkAndCheckInterrupt()
函数中找到了 LockSupport.park(this);
。就是他了!这个函数会将指定线程挂起,直至LockSupport.unpark(Thread)
被调用或者发生意外被操做系统 interrupt
。 至此, lock()
的链便通了。
咱们再来总结一下:***ReentrantLock
在上锁时,会根据实例化时指定的策略去获取锁,默认为非公平锁。若是上锁成功,锁状态值+1(重入,最大次数为 Integer.MAX_VALUE
),并将锁持有者设置为当前线程实例。在 Sync
内部维护了一个队列,存放了全部上锁失败的线程。公平锁在上锁前,会检查在本身前面是否还有其余线程等待,若是有就放弃竞争,继续等待。而非公平锁会抓住每一个机会,无论是否前面是否还有其它线程等待,只顾上锁***
至于锁释放,公平锁与非公平锁的行为就同样了。核心代码以下
// ReentrantLock#unlock() 释放锁资源
public void unlock() {
sync.release(1);
}
// Sync#release()
public final boolean release(int arg) {
// 重入锁,状态计数器减一,为0时释放
if (tryRelease(arg)) {
Node h = head;
// 释放锁时,从等待队列中获取线程并尝试唤醒
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// Sync#tryRelease() 状态计数器减一,为0时,释放锁资源,返回true
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;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// Sync#unparkSuccessor() 唤醒等待队列中的线程,让他(们)继续抢占锁
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 上锁时向队列尾部添加元素时,可能会致使队列处在中间状态,再从尾部遍历一次
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// boomya,合适的线程找到啦,将其唤醒
if (s != null)
LockSupport.unpark(s.thread);
}
复制代码
总结一下: 线程在释放锁时,将状态计数器减一(重入),当状态计数器为0时,锁可用。此时再从等待队列中寻找合适的线程唤醒,默认从队首开始,若是队列正在更新中,且未找到合适的线程,那么从队尾开始寻找。
ReentrantLock
在上锁时,会根据实例化时指定的策略去获取锁,默认为非公平锁。若是上锁成功,锁状态值+1(重入,最大次数为 Integer.MAX_VALUE
),并将锁持有者设置为当前线程实例。在 Sync
内部维护了一个队列,存放了全部上锁失败的线程。公平锁在上锁前,会检查在本身前面是否还有其余线程等待,若是有就放弃竞争,继续等待。而非公平锁会抓住每一个机会,无论是否前面是否还有其它线程等待,只顾上锁ReetrantLock
在释放锁时,将状态计数器减一(重入),当状态计数器为0时,锁可用。此时再从等待队列中寻找合适的线程唤醒,默认从队首开始,若是队列正在更新中,且未找到合适的线程,那么从队尾开始寻找。