在阅读完 JUC 包下的 AQS 源码之后,其中有很多疑问,最大的疑问就是 state 究竟是什么含义?并且 AQS 主要定义了队列的出入,但是获取资源、释放资源都是交给子类实现的,那子类是怎么实现的呢?下面开始了解 ReentrantLock。
公众号:liuzhihangs,记录工作学习中的技术、开发及源码笔记;时不时分享一些生活中的见闻感悟。欢迎大佬来指导!
一个可重入的互斥锁与隐式监视器锁synchronized具有相同的基本行为和语义,但功能更强大。
具有以下特征:
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
将通过源码及画图的方式,围绕上面几个问题,展开阅读和分析。
基本结构如图所示,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(); } } 复制代码
通过上面代码可以看出:
具体 ReentrantLock 和 AQS 的关系是怎样的,就需要通过加锁的过程来分析了。
如图所示,默认声明非公平锁,lock 方法内部调用 sync.lock();
此时应该是使用的非公平锁内部的 lock 加锁操作。
final void lock() { // 通过 CAS 设置 state 值 0 -> 1 if (compareAndSetState(0, 1)) // 设置成功当前线程获取到了锁 setExclusiveOwnerThread(Thread.currentThread()); else // 设置失败,则调用 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; } } 复制代码
acquire(1);
方法, 此处传参为 1。 流程画图如下:
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 方法,还是会试图获取一次,之后才会进入队列中。
**公平锁:**已经有节点在排队,那就自己去节点后面排队。
public boolean tryLock() { return sync.nonfairTryAcquire(1); } 复制代码
直接调用的 Sync 中的 nonfairTryAcquire, 尝试获取锁,获取失败,就返回 false,获取到锁或者是当前线程持有锁则对 state 累加后都返回 true。
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; } 复制代码
通过上面的源码及画图,基本上对开始的问题已经有了答案:
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 相关逻辑。