一、什么是ReenTranLock?
- ReentrantLock是一个可重入的互斥锁,又被称为“独占锁”。
- ReentrantLock锁在同一个时间点只能被一个线程锁持有;可重入表示,ReentrantLock锁可以被同一个线程多次获取。
- ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。在“公平锁”的机制下,线程依次排队获取锁;而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。
二、ReenTranLock源码分析
1、类的继承关系
ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。
public class ReentrantLock implements Lock, java.io.Serializable
2、类的内部类
ReentrantLock总共有三个内部类,并且三个内部类是紧密相关的,下面先看三个类的关系。
说明: ReentrantLock
类内部总共存在Sync
、NonfairSync
、FairSync
三个类,NonfairSync
与FairSync
类继承自Sync
类,Sync
类继承自AbstractQueuedSynchronizer
抽象类。下面逐个进行分析。
2.1、Sync类
abstract static class Sync extends AbstractQueuedSynchronizer {
// 序列号
private static final long serialVersionUID = -5179523762034025860L;
// 获取锁
abstract void lock();
// 非公平方式获取
final boolean nonfairTryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// 获取状态
int c = getState();
if (c == 0) { // 表示没有线程正在竞争该锁
if (compareAndSetState(0, acquires)) { // 比较并设置状态成功,状态0表示锁没有被占用
// 设置当前线程独占
setExclusiveOwnerThread(current);
return true; // 成功
}
}
else if (current == getExclusiveOwnerThread()) { // 当前线程拥有该锁
int nextc = c + acquires; // 增加重入次数
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置状态
setState(nextc);
// 成功
return true;
}
// 失败
return false;
}
// 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
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;
}
// 判断资源是否被当前线程占有
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
// 新生一个条件
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
// 返回资源的占用线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 返回状态
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
// 资源是否被占用
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
// 自定义反序列化逻辑
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
Sync类存在如下方法和作用如下。
2.2、NonfairSync类
先来看一下我们常用的使用方式 ReentrantLock 实现,一般都是直接 new 一个对象,然后通过 lock() 方法获取锁,它是怎么实现的呢,代码如下
// 1. 获取对象,默认非公平
public ReentrantLock() {
sync = new NonfairSync();
}
// 2. 调用lock() 方法
public void lock() {
sync.lock();
}
// 3. 调用非公平内部类实现的 Lock() 方法
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// 尝试获取锁
if (compareAndSetState(0, 1))
// 获取成功后设置当前线程信息
setExclusiveOwnerThread(Thread.currentThread());
else
// 未获取到锁则执行以下方法
acquire(1);
}
// 其他方法。。。
}
// acquire(1) 方法内部逻辑
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// 调用AQS 同步方法
selfInterrupt();
}
非公平锁的 acquire(int arg) ,逻辑比较简单,源码如下
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 若锁状态为0,则直接执行获取锁逻辑
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 若锁已经有线程执行,则判断是否为当前线程,若是则state + 1, 这也是可重入的实现
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
说明: 从lock方法的源码可知,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。
if (compareAndSetState(0, 1))
// 获取成功后设置当前线程信息
setExclusiveOwnerThread(Thread.currentThread());
2.3、FairSyn类
我们一起看下公平锁的源码有何不同
// 传入 true 则会使用公平锁实现 FairSync
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
static final class FairSync extends Sync {
// lock方法直接调用 acquire 方法
final void lock() {
acquire(1);
}
// ...
}
// acquire(1) 方法内部逻辑
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// 调用AQS 同步方法
selfInterrupt();
}
acquire 方法是公用的,主要看一下内部的 tryAcquire 方法,直接上源码:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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;
}
}
说明: 跟踪lock方法的源码可知,当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer
中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。
其中,FairSync
类的lock
的方法调用如下,只给出了主要的方法。
说明: 可以看出只要资源被其他线程占用,该线程就会添加到sync queue中的尾部,而不会先尝试获取资源。这也是和Nonfair最大的区别,Nonfair每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部
2.4、公平锁和非公平锁的区别
何为公平,何为非公平,在这个标榜着人人平等的社会,排队买东西大家都比较熟悉,不管是谁都得排队,先的来先买(除特殊情况)。
ReentrantLock也是相同的思想,引入了阻塞队列来实现排队等候,先来先执行,后来则排队(FIFO),所以是公平的。
那是什么是非公平呢,有人插队嘛,对吧,都插队了还谈什么公平呢,ReentrantLock的非公平锁就是不考虑阻塞队列的情况下直接获取锁。
看看源码上的区别大家就一目了然了:
// 非公平锁
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// 公平锁
final void lock() {
acquire(1);
}
非公平的进来先看看能不能拿到锁,如果拿到的话转身就溜,也不看看有没有其它线程在排队等待
再来看下 acquire 方法的流程
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 公平锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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;
}
// 非公平锁
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
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 < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
可以看出来,非公平锁在 lock() 方法里多了直接获取锁的逻辑
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
在 tryAcquire(int acquires) 方法里少了判断阻塞的逻辑,而公平锁首先会考虑是否有线程在排序等候,主要方法 hasQueuedPredecessors()
// FairSync
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
// NonfairSync
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
3、类的属性
ReentrantLock
类的sync
非常重要,对ReentrantLock
类的操作大部分都直接转化为对Sync
和AbstractQueuedSynchronizer
类的操作
public class ReentrantLock implements Lock, java.io.Serializable {
// 序列号
private static final long serialVersionUID = 7373984872572414699L;
// 同步队列
private final Sync sync;
}
4、类的构造函数
- ReentrantLock()型构造函数
默认是采用的非公平策略获取锁
public ReentrantLock() {
// 默认非公平策略
sync = new NonfairSync();
}
- ReentrantLock(boolean)型构造函数
可以传递参数确定采用公平策略或者是非公平策略,参数为true表示公平策略,否则,采用非公平策略:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
5、核心函数分析
通过分析ReentrantLock
的源码,可知对其操作都转化为对Sync
对象的操作,由于Sync
继承了AQS,所以基本上都可以转化为对AQS
的操作。如将ReentrantLock的lock
函数转化为对Sync
的lock
函数的调用,而具体会根据采用的策略(如公平策略或者非公平策略)的不同而调用到Sync
的不同子类
具体分析可见AQS的例子AQS
三、面试题目
1、什么是可重入,什么是可重入锁? 它用来解决什么问题?
可重入就是说某个线程已经得到某个锁,能够再次获取锁;可重入锁就是可以被重复获得的锁;它是用来解决死锁问题的
2、ReentrantLock的核心是AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。
ReentrantLock总共有三个内部类,并且三个内部类是紧密相关的,下面先看三个类的关系。
说明: ReentrantLock
类内部总共存在Sync
、NonfairSync
、FairSync
三个类,NonfairSync
与FairSync
类继承自Sync
类,Sync
类继承自AbstractQueuedSynchronizer
抽象类。
3、ReentrantLock是如何实现公平锁的?
不用于非公平锁,公平锁在当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer
中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则,如果不存在,才会获取锁。
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && // 判断队列中是否有等待时间更长的线程
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
4、ReentrantLock是如何实现非公平锁的?
不同于公平锁,非公平锁每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。
首先体现在lock()方法上,先通过CAS原子命令判断当前锁是否可以占用,可以直接获取。
final void lock() {
// 尝试获取锁
if (compareAndSetState(0, 1))
// 获取成功后设置当前线程信息
setExclusiveOwnerThread(Thread.currentThread());
else
// 未获取到锁则执行以下方法
acquire(1);
}
其次不同于公平锁,在triAcquire()的过程中,它是不用去比较!hasQueuedPredecessors()
,也就是不用去看有没有比他等待更久的线程,而是直接去拿锁
int c = getState();
// 若锁状态为0,则直接执行获取锁逻辑
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
5、ReentrantLock默认实现的是公平还是非公平锁?
非公平锁