java底层AQS实现类kReentrantLock锁的构成及源码解析

目录
  • 引导语
  • 1、类注释
  • 2、类结构
  • 3、构造器
  • 4、Sync同步器
    • 4.1、nonfairTryAcquire
    • 4.2、tryRelease
  • 5、FairSync公平锁
  • 6、NonfairSync非公平锁
  • 7、如何串起来
    • 7.1lock加锁
    • 7.2tryLock尝试加锁
    • 7.3unlock释放锁
    • 7.4Condition
  • 8、总结

引导语

本章的描述思路是先描述清楚 ReentrantLock 的构成组件,然后使用加锁和释放锁的方法把这些组件串起来。

1、类注释

ReentrantLock 中文我们习惯叫做可重入互斥锁,可重入的意思是同一个线程可以对同一个共享资源重复的加锁或释放锁,互斥就是 AQS 中的排它锁的意思,只允许一个线程获得锁。

我们来一起来看下类注释上都有哪些重要信息:

  • 可重入互斥锁,和 synchronized 锁具有同样的功能语义,但更有扩展性;
  • 构造器接受 fairness 的参数,fairness 是 ture 时,保证获得锁时的顺序,false 不保证;
  • 公平锁的吞吐量较低,获得锁的公平性不能代表线程调度的公平性;
  • tryLock() 无参方法没有遵循公平性,是非公平的(lock 和 unlock 都有公平和非公平,而 tryLock 只有公平锁,所以单独拿出来说一说)。

我们补充一下第二点,ReentrantLock 的公平和非公平,是针对获得锁来说的,如果是公平的,可以保证同步队列中的线程从头到尾的顺序依次获得锁,非公平的就无法保证,在释放锁的过程中,我们是没有公平和非公平的说法的。

2、类结构

ReentrantLock 类本身是不继承 AQS 的,实现了 Lock 接口,如下:

public class ReentrantLock implements Lock, java.io.Serializable {}

Lock 接口定义了各种加锁,释放锁的方法,接口有如下几个:

// 获得锁方法,获取不到锁的线程会到同步队列中阻塞排队
void lock();
// 获取可中断的锁
void lockInterruptibly() throws InterruptedException;
// 尝试获得锁,如果锁空闲,立马返回 true,否则返回 false
boolean tryLock();
// 带有超时等待时间的锁,如果超时时间到了,仍然没有获得锁,返回 false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 得到新的 Condition
Condition newCondition();

ReentrantLock 就负责实现这些接口,我们使用时,直接面对的也是这些方法,这些方法的底层实现都是交给 Sync 内部类去实现的,Sync 类的定义如下:

abstract static class Sync extends AbstractQueuedSynchronizer {}

Sync 继承了 AbstractQueuedSynchronizer ,所以 Sync 就具有了锁的框架,根据 AQS 的框架,Sync 只需要实现 AQS 预留的几个方法即可,但 Sync 也只是实现了部分方法,还有一些交给子类 NonfairSync 和 FairSync 去实现了,NonfairSync 是非公平锁,FairSync 是公平锁,定义如下:

// 同步器 Sync 的两个子类锁
static final class FairSync extends Sync {}
static final class NonfairSync extends Sync {}

几个类整体的结构如下:

图中 Sync、NonfairSync、FairSync 都是静态内部类的方式实现的,这个也符合 AQS 框架定义的实现标准。

3、构造器

ReentrantLock 构造器有两种,代码如下:

// 无参数构造器,相当于 ReentrantLock(false),默认是非公平的
public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

无参构造器默认构造是非公平的锁,有参构造器可以选择。

从构造器中可以看出,公平锁是依靠 FairSync 实现的,非公平锁是依靠 NonfairSync 实现的。

4、Sync 同步器

Sync 表示同步器,继承了 AQS,UML 图如下:

从 UML 图中可以看出,lock 方法是个抽象方法,留给 FairSync 和 NonfairSync 两个子类去实现,我们一起来看下剩余重要的几个方法。

4.1、nonfairTryAcquire

// 尝试获得非公平锁
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;
        }
    }
    // 如果当前线程已经持有锁了,同一个线程可以对同一个资源重复加锁,代码实现的是可重入锁
    else if (current == getExclusiveOwnerThread()) {
        // 当前线程持有锁的数量 + acquires
        int nextc = c + acquires;
        // int 是有最大值的,<0 表示持有锁的数量超过了 int 的最大值
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    //否则线程进入同步队列
    return false;
}

以上代码有三点需要注意:

通过判断 AQS 的 state 的状态来决定是否可以获得锁,0 表示锁是空闲的;else if 的代码体现了可重入加锁,同一个线程对共享资源重入加锁,底层实现就是把 state + 1,并且可重入的次数是有限制的,为 Integer 的最大值;这个方法是非公平的,所以只有非公平锁才会用到,公平锁是另外的实现。

无参的 tryLock 方法调用的就是此方法,tryLock 的方法源码如下:

public boolean tryLock() {
    // 入参数是 1 表示尝试获得一次锁
    return sync.nonfairTryAcquire(1);
}

4.2、tryRelease

// 释放锁方法,非公平和公平锁都使用
protected final boolean tryRelease(int releases) {
    // 当前同步器的状态减去释放的个数,releases 一般为 1
    int c = getState() - releases;
    // 当前线程根本都不持有锁,报错
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果 c 为 0,表示当前线程持有的锁都释放了
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 如果 c 不为 0,那么就是可重入锁,并且锁没有释放完,用 state 减去 releases 即可,无需做其他操作
    setState(c);
    return free;
}

tryRelease 方法是公平锁和非公平锁都公用的,在锁释放的时候,是没有公平和非公平的说法的。

从代码中可以看到,锁最终被释放的标椎是 state 的状态为 0,在重入加锁的情况下,需要重入解锁相应的次数后,才能最终把锁释放,比如线程 A 对共享资源 B 重入加锁 5 次,那么释放锁的话,也需要释放 5 次之后,才算真正的释放该共享资源了。

5、FairSync 公平锁

FairSync 公平锁只实现了 lock 和 tryAcquire 两个方法,lock 方法非常简单,如下:

// acquire 是 AQS 的方法,表示先尝试获得锁,失败之后进入同步队列阻塞等待
final void lock() {
    acquire(1);
}

tryAcquire 方法是 AQS 在 acquire 方法中留给子类实现的抽象方法,FairSync 中实现的源码如下:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // hasQueuedPredecessors 是实现公平的关键
        // 会判断当前线程是不是属于同步队列的头节点的下一个节点(头节点是释放锁的节点)
        // 如果是(返回false),符合先进先出的原则,可以获得锁
        // 如果不是(返回true),则继续等待
        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;
}

代码和 Sync 的 nonfairTryAcquire 方法实现类似,唯一不同的是在获得锁时使用 hasQueuedPredecessors 方法体现了其公平性。

6、NonfairSync 非公平锁

NonfairSync 底层实现了 lock 和 tryAcquire 两个方法,如下:

// 加锁
final void lock() {
    // cas 给 state 赋值
    if (compareAndSetState(0, 1))
        // cas 赋值成功,代表拿到当前锁,记录拿到锁的线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // acquire 是抽象类AQS的方法,
        // 会再次尝试获得锁,失败会进入到同步队列中
        acquire(1);
}
// 直接使用的是 Sync.nonfairTryAcquire 方法
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

7、如何串起来

以上内容主要说了 ReentrantLock 的基本结构,比较零散,那么这些零散的结构如何串联起来呢?我们是通过 lock、tryLock、unlock 这三个 API 将以上几个类串联起来,我们来一一看下。

7.1 lock 加锁

lock 的代码实现:

public void lock() {
    sync.lock();
}

其底层的调用关系(只是简单表明调用关系,并不是完整分支图)如下:

7.2 tryLock 尝试加锁

tryLock 有两个方法,一种是无参的,一种提供了超时时间的入参,两种内部是不同的实现机制,代码分别如下:

// 无参构造器
public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}
// timeout 为超时的时间,在时间内,仍没有得到锁,会返回 false
public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

接着我们一起看下两个 tryLock 的调用关系图,下图显示的是无参 tryLock 的调用关系图,如下:

我们需要注意的是 tryLock 无参方法底层走的就是非公平锁实现,没有公平锁的实现。

下图展示的是带有超时时间的有参 tryLock 的调用实现图:

7.3 unlock 释放锁

unlock 释放锁的方法,底层调用的是 Sync 同步器的 release 方法,release 是 AQS 的方法,分成两步:

尝试释放锁,如果释放失败,直接返回 false;释放成功,从同步队列的头节点的下一个节点开始唤醒,让其去竞争锁。

第一步就是我们上文中 Sync 的 tryRelease 方法(4.1),第二步 AQS 已经实现了。

unLock 的源码如下:

// 释放锁
public void unlock() {
    sync.release(1);
}

7.4 Condition

ReentrantLock 对 Condition 并没有改造,直接使用 AQS 的 ConditionObject 即可。

8、总结

这就是我们在研究完 AQS 源码之后,碰到的第一个锁,是不是感觉很简单,AQS 搭建了整个锁架构,子类锁只需要根据场景,实现 AQS 对应的方法即可,不仅仅是 ReentrantLock 是这样,JUC 中的其它锁也都是这样,只要对 AQS 了如指掌,锁其实非常简单。

以上就是java底层AQS实现类kReentrantLock锁的构成及源码解析的详细内容,更多关于AQS实现类ReentrantLock锁的构成的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java并发之ReentrantLock类源码解析

    ReentrantLock内部由Sync类实例实现. Sync类定义于ReentrantLock内部. Sync继承于AbstractQueuedSynchronizer. AbstractQueuedSynchronizer继承于AbstractOwnableSynchronizer. AbstractOwnableSynchronizer类中只定义了一个exclusiveOwnerThread变量,表示当前拥有的线程. 除了Sync类,ReentrantLock内部还定义了两个实现类. No

  • Java源码解析之可重入锁ReentrantLock

    本文基于jdk1.8进行分析. ReentrantLock是一个可重入锁,在ConcurrentHashMap中使用了ReentrantLock. 首先看一下源码中对ReentrantLock的介绍.如下图.ReentrantLock是一个可重入的排他锁,它和synchronized的方法和代码有着相同的行为和语义,但有更多的功能.ReentrantLock是被最后一个成功lock锁并且还没有unlock的线程拥有着.如果锁没有被别的线程拥有,那么一个线程调用lock方法,就会成功获取锁并返回.

  • ReentrantLock实现原理详解

    以下是本篇文章的大纲 1 synchronized和lock     1.1 synchronized的局限性     1.2 Lock简介 2 AQS 3 lock()与unlock()实现原理     3.1 基础知识     3.2 内部结构     3.3 NonfairSync     3.3.1 lock()     3.3.2 unlock()     3.3.3 小结     3.4 FairSync 4 超时机制 5 总结 1 synchronized和lock 1.1 syn

  • Java并发系列之ReentrantLock源码分析

    在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可见性.在大多数情况下,这些机制都能很好地完成工作,但却无法实现一些更高级的功能,例如,无法中断一个正在等待获取锁的线程,无法实现限定时间的获取锁机制,无法实现非阻塞结构的加锁规则等.而这些更灵活的加锁机制通常都能够提供更好的活跃性或性能.因此,在Java5.0中增加了一种新的机制:Reentrant

  • Java源码解析之详解ReentrantLock

    ReentrantLock ReentrantLock是一种可重入的互斥锁,它的行为和作用与关键字synchronized有些类似,在并发场景下可以让多个线程按照一定的顺序访问同一资源.相比synchronized,ReentrantLock多了可扩展的能力,比如我们可以创建一个名为MyReentrantLock的类继承ReentrantLock,并重写部分方法使其更加高效. 当一个线程调用ReentrantLock.lock()方法时,如果ReentrantLock没有被其他线程持有,且不存在

  • java底层AQS实现类kReentrantLock锁的构成及源码解析

    目录 引导语 1.类注释 2.类结构 3.构造器 4.Sync同步器 4.1.nonfairTryAcquire 4.2.tryRelease 5.FairSync公平锁 6.NonfairSync非公平锁 7.如何串起来 7.1lock加锁 7.2tryLock尝试加锁 7.3unlock释放锁 7.4Condition 8.总结 引导语 本章的描述思路是先描述清楚 ReentrantLock 的构成组件,然后使用加锁和释放锁的方法把这些组件串起来. 1.类注释 ReentrantLock 中

  • Java源码解析之object类

    在源码的阅读过程中,可以了解别人实现某个功能的涉及思路,看看他们是怎么想,怎么做的.接下来,我们看看这篇Java源码解析之object的详细内容. Java基类Object java.lang.Object,Java所有类的父类,在你编写一个类的时候,若无指定父类(没有显式extends一个父类)编译器(一般编译器完成该步骤)会默认的添加Object为该类的父类(可以将该类反编译看其字节码,不过貌似Java7自带的反编译javap现在看不到了). 再说的详细点:假如类A,没有显式继承其他类,编译

  • java.lang.Void类源码解析

    在一次源码查看ThreadGroup的时候,看到一段代码,为以下: /* * @throws NullPointerException if the parent argument is {@code null} * @throws SecurityException if the current thread cannot create a * thread in the specified thread group. */ private static Void checkParentAcc

  • java源码解析之String类的compareTo(String otherString)方法

    一. 前言 最近我发现了一个事情,那就是在面试笔试中,好多公司都喜欢在String字符串上出问题,涉及到方方面面的知识,包括其中的一些常用方法. String 类代表字符串.Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现. 字符串是常量:它们的值在创建之后不能更改.字符串缓冲区支持可变的字符串.因为 String 对象是不可变的,所以可以共享. 近日研究了一下String类的一些方法, 通过查看源码, 对一些常用的方法也有了更透彻的认识, 也让我更加理解了设

  • Java源码解析重写锁的设计结构和细节

    目录 引导语 1.需求 2.详细设计 2.1.定义锁 2.2.定义同步器Sync 2.3.通过能否获得锁来决定能否得到链接 3.测试 4.总结 引导语 有的面试官喜欢让同学在说完锁的原理之后,让你重写一个新的锁,要求现场在白板上写出大概的思路和代码逻辑,这种面试题目,蛮难的,我个人觉得其侧重点主要是两个部分: 考察一下你对锁原理的理解是如何来的,如果你对源码没有解读过的话,只是看看网上的文章,或者背面试题,也是能够说出大概的原理,但你很难现场写出一个锁的实现代码,除非你真的看过源码,或者有和锁相

  • Java 线程池ThreadPoolExecutor源码解析

    目录 引导语 1.整体架构图 1.1.类结构 1.2.类注释 1.3.ThreadPoolExecutor重要属性 2.线程池的任务提交 3.线程执行完任务之后都在干啥 4.总结 引导语 线程池我们在工作中经常会用到.在请求量大时,使用线程池,可以充分利用机器资源,增加请求的处理速度,本章节我们就和大家一起来学习线程池. 本章的顺序,先说源码,弄懂原理,接着看一看面试题,最后看看实际工作中是如何运用线程池的. 1.整体架构图 我们画了线程池的整体图,如下: 本小节主要就按照这个图来进行 Thre

  • CountDownLatch和Atomic原子操作类源码解析

    目录 引导语 1.CountDownLatch 1.1.await 1.2.countDown 1.3.示例 2.Atomic原子操作类 3.总结 引导语 本小节和大家一起来看看 CountDownLatch 和 Atomic 打头的原子操作类,CountDownLatch 的源码非常少,看起来比较简单,但 CountDownLatch 的实际应用却不是很容易:Atomic 原子操作类就比较好理解和应用,接下来我们分别来看一下. 1.CountDownLatch CountDownLatch 中

  • Springboot基于Redisson实现Redis分布式可重入锁源码解析

    目录 一.前言 二.为什么使用Redisson 1.我们打开官网 2.我们可以看到官方让我们去使用其他 3.打开官方推荐 4.找到文档 三.Springboot整合Redisson 1.导入依赖 2.以官网为例查看如何配置 3.编写配置类 4.官网测试加锁例子 5.根据官网简单Controller接口编写 6.测试 四.lock.lock()源码分析 1.打开RedissonLock实现类 2.找到实现方法 3.按住Ctrl进去lock方法 4.进去尝试获取锁方法 5.查看tryLockInne

  • ReentrantLock从源码解析Java多线程同步学习

    目录 前言 管程 管程模型 MESA模型 主要特点 AQS 共享变量 资源访问方式 主要方法 队列 node节点等待状态 ReentrantLock源码分析 实例化ReentrantLock 加锁 A线程加锁成功 B线程尝试加锁 释放锁 总结 前言 如今多线程编程已成为了现代软件开发中的重要部分,而并发编程中的线程同步问题更是一道难以逾越的坎.在Java语言中,synchronized是最基本的同步机制,但它也存在着许多问题,比如可重入性不足.死锁等等.为了解决这些问题,Java提供了更加高级的

随机推荐