共计 6052 个字符,预计需要花费 16 分钟才能阅读完成。
前言
在浏览完 JUC 包下的 AQS 源码之后,其中有很多疑难,最大的疑难就是 state 到底是什么含意?并且 AQS 次要定义了队列的出入,然而获取资源、开释资源都是交给子类实现的,那子类是怎么实现的呢?上面开始理解 ReentrantLock。
公众号:『刘志航』,记录工作学习中的技术、开发及源码笔记;时不时分享一些生存中的见闻感悟。欢送大佬来领导!
介绍
一个可重入的互斥锁与隐式监视器锁 synchronized 具备雷同的根本行为和语义,但性能更弱小。
具备以下特色:
- 互斥性:同时只有一个线程能够获取到该锁,此时其余线程申请获取锁,会被阻塞,而后被放到该锁外部保护的一个 AQS 阻塞队列中。
- 可重入性:保护 state 变量,初始为 0,当一个线程获取到锁时,state 应用 cas 更新为 1,本线程再次申请获取锁,会对 state 进行 CAS 递增,反复获取次数即 state,最多为 2147483647。试图超出此限度会从锁定办法抛出 Error。
- 偏心 / 非公平性:在初始化时,能够通过结构器传参,指定是否为偏心锁,还是非偏心锁。当设置为 true 时,为偏心锁,线程争用锁时,会偏向于等待时间最长的线程。
根本应用
class X {private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {lock.lock(); // block until condition holds
try {// ... method body} finally {lock.unlock()
}
}
}
问题疑难?
首先在浏览本文时,对 AQS 有了肯定的理解,如果不理解的话,能够看一下之前的文章。图文解说 AQS
- 在 AQS 中介绍 state 时,说 state 含意由子类进行定义,那在 ReentrantLock 中 state 代表什么?
- ReentrantLock 和 AQS 有什么关系?
- 线程是如何获取到锁的?
- 锁的可重入性是如何实现的?
- 以后线程获取锁失败,被阻塞的后续操作是什么?
- 偏心锁和非偏心锁是如何体现的?
- 锁是如何开释的?
将通过源码及画图的形式,围绕下面几个问题,开展浏览和剖析。
源码剖析
根本构造
根本构造如图所示,ReentrantLock 类实现了接口 Lock,在接口 Lock 中定义了应用锁时的办法,办法及含意如下:
public interface Lock {
// 获取锁,如果没有获取到,会阻塞。void lock();
// 获取锁,如果没有获取到,会阻塞。响应中断。void lockInterruptibly() throws InterruptedException;
// 尝试获取锁,如果获取到,返回 true,没有获取到 返回 false
boolean tryLock();
// 尝试获取锁,没有有获取到,会期待指定工夫,响应中断。boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 开释锁
void unlock();}
而 ReentrantLock 也只是实现了 Lock 接口,并实现了这些办法,那 ReentrantLock 和 AQS 到底有什么关系呢?这就须要看外部具体如何实现的了。
通过下面类图能够看出,在 ReentrantLock 中含有两个外部类,别离是 NonfairSync FairSync 而它俩又实现了 抽象类 Sync,抽象类 Sync 继承了 AbstractQueuedSynchronizer 即 AQS。具体代码如下:
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
// 锁的同步控制根底类。子类具体到偏心和非偏心的版本。应用 AQS 状态来示意持有该锁的数量。abstract static class Sync extends AbstractQueuedSynchronizer {// 省略 ...}
static final class NonfairSync extends Sync {// 非偏心锁逻辑 省略 ...}
static final class FairSync extends Sync {// 偏心锁逻辑 省略 ...}
// 默认非偏心锁
public ReentrantLock() {sync = new NonfairSync();
}
// 依据传参指定偏心锁还是非偏心锁,true 偏心锁,false 非偏心锁
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
}
通过下面代码能够看出:
- 锁的根本管制是由 NonfairSync 和 FairSync 进行管制的,而它俩的父类 Sync 继承了 AQS (AbstractQueuedSynchronizer),这也就是说明 ReentrantLock 的实现和 AQS 是无关的。
- NonfairSync 代表非偏心锁实现逻辑,FairSync 代表偏心锁实现逻辑。
- 结构器传参能够看出,初始化时,默认为 NonfairSync 非偏心锁。也能够指定申明为偏心锁或非偏心锁,传参 true 为 偏心锁,false 为非偏心锁。
具体 ReentrantLock 和 AQS 的关系是怎么的,就须要通过加锁的过程来剖析了。
lock
如图所示,默认申明非偏心锁,lock 办法外部调用 sync.lock();
此时应该是应用的非偏心锁外部的 lock 加锁操作。
final void lock() {
// 通过 CAS 设置 state 值 0 -> 1
if (compareAndSetState(0, 1))
// 设置胜利以后线程获取到了锁
setExclusiveOwnerThread(Thread.currentThread());
else
// 设置失败,则调用 AQS 的办法,尝试获取锁。acquire(1);
}
- 首先会 应用 CAS 更新 state 的值,此时就会发现,state 在这里代表的锁的状态。0 未加锁,1 加锁。
- 设置失败,会调用 AQS 的 acquire(1); 办法。
再看下 AQS 的 acquire 代码:
public final void acquire(int arg) {
// tryAcquire 尝试获取 state,获取失败则会退出到队列
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();}
在之前剖析 AQS 源码时,曾经介绍 tryAcquire 是尝试获取 state 的值,AQS 中并不提供可用的办法,此处是由子类实现的。所以这块代码还是在 NonfairSync 类中本人实现的业务逻辑。
static final class NonfairSync extends Sync {
// NonfairSync 实现
protected final boolean tryAcquire(int acquires) {
// 调用父类的办法
return nonfairTryAcquire(acquires);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
// NonfairSync 的父类 Sync 中有实现
// state 传参是 1
final boolean nonfairTryAcquire(int acquires) {
// 获取以后线程
final Thread current = Thread.currentThread();
// 获取 state
int c = getState();
// 如果 c 是 0
if (c == 0) {
// 应用 cas 更新为 1
if (compareAndSetState(0, acquires)) {
// 设置持有线程为以后
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// 如果是以后线程持有
// 对 state 进行累加
int nextc = c + acquires;
// 不容许超过 int 的最大值 2147483647 + 1 = -2147483648
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置 state 的值
setState(nextc);
return true;
}
return false;
}
}
- 以后线程加锁,间接应用 CAS 形式对 state 从 0 更新为 1,更新胜利,则取得锁,更新失败,则获取失败。
- 更新失败后会调用 AQS 的
acquire(1);
办法,此处传参为 1。 -
tryAcquire 再次尝试获取锁。
- state 是 0,尝试获取。获取胜利返回 true;
- state 不是 0,判断是否为以后线程持有,是以后线程持有则对 state 进行累加。
- tryAcquire 获取锁失败,则走 AQS 的 acquireQueued 逻辑,创立节点,并退出到期待队列中。
流程画图如下:
- 初始为单个线程
- 此时其余线程来申请获取锁
- 加锁流程图
偏心锁是如何体现的
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {acquire(1);
}
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;
}
}
拉进去代码比拟一下:
能够看出在偏心锁(FairSync)中多了一个判断条件
!hasQueuedPredecessors()
hasQueuedPredecessors 办法在 AQS 中,如果有以后线程后面的线程排队返回 true,如果以后线程是在队列的头部或队列为空,返回 false。
代码如下:
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
如果以后加锁时曾经有节点在排队,那就去节点尾部排队,否则才会去抢占锁。
到这里基本上曾经晓得偏心锁和非偏心锁的区别了:
非偏心锁: 不论有没有节点在排队,都会试图去获取锁,如果获取失败,进入 acquire 办法,还是会试图获取一次,之后才会进入队列中。
偏心锁: 曾经有节点在排队,那就本人去节点前面排队。
tryLock
public boolean tryLock() {return sync.nonfairTryAcquire(1);
}
间接调用的 Sync 中的 nonfairTryAcquire,尝试获取锁,获取失败,就返回 false,获取到锁或者是以后线程持有锁则对 state 累加后都返回 true。
unlock
public void unlock() {sync.release(1);
}
发现 unlock 间接调用的 AQS 的 release 办法,进行开释资源。
public final boolean release(int arg) {if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
这块在 AQS 中有介绍,也阐明 tryRelease 由子类进行实现,当初在 ReentrantLock 重点关注 tryRelease 的实现。
// 开释资源,传入值为 1
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;
}
- 获取以后的 state 进行 -1 操作;
- 判断了下以后线程是否为持有线程;
- 如果开释完之后 state 为 0,则设置持有线程为 null;
- 更新并返回 state 的值。
总结
通过下面的源码及画图,基本上对开始的问题曾经有了答案:
Q:在 AQS 中介绍 state 时,说 state 含意由子类进行定义,那在 ReentrantLock 中 state 代表什么?
A:在 ReentrantLock 中 state 代表加锁状态,0 没有线程取得锁,大于等于 1 曾经有线程取得锁,大于 1 阐明该取得锁的线程多次重入。
Q:ReentrantLock 和 AQS 有什么关系?
A:ReentrantLock 外部基于 AQS 实现,无论是锁状态,还是进入期待队列,锁开释等都是基于 AQS 实现。ReentrantLock 的偏心锁和非偏心锁都是 NonfairSync、FairSync 来实现的,而他们的父类 Sync 继承了 AQS。
Q:线程是如何获取到锁的?
A:线程通过批改 state 字段的状态来获取到锁。
Q:锁的可重入性是如何实现的?
A:以后线程发现 state 不是 0,则阐明有锁曾经被获取了,此时会判断以后获取到锁的线程是不是本人,如果是,则对 state 进行累加。
Q:以后线程获取锁失败,被阻塞的后续操作是什么?
A:获取失败,会放到 AQS 期待队列中,在队列中一直循环,监督前一个节点是否为 head,是的话,会从新尝试获取锁。
Q:偏心锁和非偏心锁是如何体现的?
A:偏心锁次要体现在如果以后队列中曾经有排队的线程了,则本人间接排在前面。非偏心锁是不论以后队列都没有线程排队,都会间接尝试批改 state 获取锁。
Q:锁是如何开释的?
A:锁开释资源,行将 state 进行 -1 操作,如果 -1 后 state 为 0,则开释节点,后续节点尝试获取锁。此处能够看 AQS 相干逻辑。