干货,深入剖析ReentrantLock源码,推荐收藏
ReentrantLock和Synchronized都是Java开发中最常用的锁,与Synchronized这种JVM内置锁不同的是,ReentrantLock提供了更丰富的语义。可以创建公平锁或非公平锁、响应中断、超时等待、按条件唤醒等。在某些场景下,使用ReentrantLock更适合,功能更强大。
前两篇文章,我们分析了AQS的加锁流程、以及源码实现。当时我们就说了,AQS使用了模板设计模式,父类中定义加锁流程,子类去实现具体的加锁逻辑。所以大部分加锁代码已经在父类AQS中实现了,导致ReentrantLock的源码非常简单,一块学习一下。
先看一下ReentrantLock怎么使用?
1. ReentrantLock的使用
/**
* @author 一灯架构
* @apiNote ReentrantLock示例
**/
public class ReentrantLockDemo {
public static void main(String[] args) {
// 1. 创建ReentrantLock对象
ReentrantLock lock = new ReentrantLock();
// 2. 加锁
lock.lock();
try {
// 3. 这里执行具体的业务逻辑
} finally {
// 4. 释放锁
lock.unlock();
}
}
}
可以看到ReentrantLock的使用非常简单,调用lock加锁,unlock释放锁,需要配置try/finally使用,保证在代码执行出错的时候也能释放锁。
ReentrantLock也可以配合Condition条件使用,具体可以翻一下前几篇文章中BlockingQueue的源码解析,那里面有ReentrantLock的实际使用。
再看一下ReentrantLock的类结构
2. ReentrantLock类结构
// 实现Lock接口
public class ReentrantLock implements Lock {
// 只有一个Sync同步变量
private final Sync sync;
// Sync继承自AQS,主要逻辑都在这里面
abstract static class Sync extends AbstractQueuedSynchronizer {
}
// Sync的两个子类,分别实现了公平锁和非公平锁
static final class FairSync extends Sync {
}
static final class NonfairSync extends Sync {
}
}
可以看出ReentrantLock的类结构非常简单,实现了Lock接口。
类里面有两个静态内部类,分别实现公平锁和非公平锁。
看一下Lock接口中,定义了哪些方法?
public interface Lock {
// 加锁
void lock();
// 加可中断的锁
void lockInterruptibly() throws InterruptedException;
// 尝试加锁
boolean tryLock();
// 一段时间内,尝试加锁
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 新建条件状态
Condition newCondition();
}
就是一些使用锁的常用方法。
在上篇文章中浏览AQS源码的时候,了解到AQS定义了一些有关具体加锁、释放锁的抽象方法,留给子类去实现,再看一下有哪些抽象方法:
// 加独占锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 释放独占锁
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
// 加共享锁
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
// 释放共享锁
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
// 判断是否是当前线程正在持有锁
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
由于ReentrantLock使用的是独占锁,所以只需要实现独占锁相关的方法就可以了。
3. ReentrantLock源码解析
3.1 ReentrantLock构造方法
// 默认的构造方法,使用非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 传true,可以指定使用公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
在创建ReentrantLock对象的时候,可以指定使用公平锁还是非公平锁,默认使用非公平锁,显然非公平锁的性能更好。
先思考一个面试常考问题,公平锁和非公平锁是怎么实现的?
3.2 非公平锁源码
先看一下加锁源码:
从父类ReentrantLock的加锁方法入口:
public class ReentrantLock implements Lock {
// 加锁入口方法
public void lock() {
// 调用Sync中加锁方法
sync.lock();
}
}
在子类NonfairSync的加锁方法:
// 非公平锁
static final class NonfairSync extends Sync {
// 加锁
final void lock() {
// 1. 先尝试加锁(使用CAS设置state=1)
if (compareAndSetState(0, 1))
// 2. 加锁成功,就把当前线程设置为持有锁线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 3. 没加锁成功,再调用父类AQS中实际的加锁逻辑
acquire(1);
}
}
加锁逻辑也很简单,先尝试使用CAS加锁(也就是把state从0设置成1),加锁成功,就把当前线程设置为持有锁线程。
设计者很聪明,在锁竞争不激烈的情况下,很大概率可以加锁成功,也就不用走else中复杂的加锁逻辑了。
如果没有加锁成功,还是需要走else中调用父类AQS的acquire方法,而acquire又需要调用子类的tryAcquire方法。
调用链路就是下面这样:

根据调用链路,实际的加锁逻辑在Sync.nonfairTryAcquire方法里面。
abstract static class Sync extends AbstractQueuedSynchronizer {
// 非公平锁的最终加锁方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 1. 获取同步状态
int c = getState();
// 2. state=0表示无锁,先尝试加锁(使用CAS设置state=1)
if (c == 0) {
if (compareAndSetState(0, acquires)) {
// 3. 加锁成功,就把当前线程设置为持有锁线程
setExclusiveOwnerThread(current);
return true;
}
// 4. 如果当前线程已经持有锁,执行可重入的逻辑
} else if (current == getExclusiveOwnerThread()) {
// 5. 加锁次数+acquires
int nextc = c + acquires;
// 6. 超过tnt类型最大值,溢出了
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
再看一下释放锁的调用流程,公平锁和非公平锁流程是一样的,最终都是执行Sync.tryRelease方法:

abstract static class Sync extends AbstractQueuedSynchronizer {
// 释放锁
protected final boolean tryRelease(int releases) {
// 1. 同步状态减去释放锁次数
int c = getState() - releases;
// 2. 校验当前线程不持有锁,就报错
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 3. 判断同步状态是否等于0,无锁后,就删除持有锁的线程
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
再看一下公平锁的源码
3.3 公平锁源码
先看一下公平锁的加锁流程:

最终的加锁方法是FairSync.tryAcquire,看一下具体逻辑:
static final class FairSync extends Sync {
// 实现父类的加锁逻辑
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 1. 获取同步状态
int c = getState();
// 2. state=0表示无锁,先尝试加锁(使用CAS设置state=1)
if (c == 0) {
// 3. 判断当前线程是不是头节点的下一个节点(讲究先来后到)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
// 4. 如果当前线程已经持有锁,执行可重入的逻辑
} else if (current == getExclusiveOwnerThread()) {
// 5. 加锁次数+acquires
int nextc = c + acquires;
// 6. 超过tnt类型最大值,溢出了
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// 判断当前线程是不是头节点的下一个节点(讲究先来后到)
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
}
公平锁的释放锁逻辑跟非公平锁一样,上面已经讲过。
4. 总结
看完了ReentrantLock的所有源码,是不是觉得ReentrantLock很简单。
由于加锁流程的编排工作已经在父类AQS中实现,子类只需要实现具体的加锁逻辑即可。
加锁逻辑也很简单,也就是修改同步状态state的值和持有锁的线程exclusiveOwnerThread。
我是「一灯架构」,如果本文对你有帮助,欢迎各位小伙伴点赞、评论和关注,感谢各位老铁,我们下期见

干货,深入剖析ReentrantLock源码,推荐收藏的更多相关文章
- Java并发系列[5]----ReentrantLock源码分析
在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...
- 死磕 java同步系列之ReentrantLock源码解析(二)——条件锁
问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务场景自己无法处理,而需要等 ...
- Java并发编程笔记之ReentrantLock源码分析
ReentrantLock是可重入的独占锁,同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞后放入该锁的AQS阻塞队列里面. 首先我们先看一下ReentrantLock的类图结构,如下图所示 ...
- Java并发编程-ReentrantLock源码分析
一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...
- 第六章 ReentrantLock源码解析2--释放锁unlock()
最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final Reentrant ...
- java多线程---ReentrantLock源码分析
ReentrantLock源码分析 基础知识复习 synchronized和lock的区别 synchronized是非公平锁,无法保证线程按照申请锁的顺序获得锁,而Lock锁提供了可选参数,可以配置 ...
- ReentrantLock源码分析--jdk1.8
JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...
- JUC AQS ReentrantLock源码分析
警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. Java的内置锁一直都是备受争议的,在JDK1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6 ...
- java源码-ReentrantLock源码分析-1
ReentrantLock 继承于lock是比较常用的独占锁,接下来我们来分析一下ReentrantLock源码以及接口设计: Sync是ReentrantLock的内部静态抽象类继承Abstract ...
随机推荐
- 【Java】学习路径57-TCP协议客户端与服务器端的关闭
在TCP协议中,如果发送端(客户端)关闭了,那么接收端(服务器端)端就会收到这个消息. 那么接收端(服务器端)怎么知道的呢? 我们进行实验: 首先在发送端中编写一段程序,当用户输入"end& ...
- 【java】非常多!学习路径24-总结目前所有知识(上)
感谢sikiedu.com的siki老师.几年前就开始看siki的课程,最近突然想写这个笔记系列,顺便回顾一下这些基础的知识,同时也希望能帮助到一些人,有问题一起交流哈. 全文共十章,大约1.5万字, ...
- 03_Django-GET请求和POST请求-设计模式及模板层
03_Django-GET请求和POST请求-设计模式及模板层 视频:https://www.bilibili.com/video/BV1vK4y1o7jH 博客:https://blog.csdn. ...
- docker存储管理及实例
一.Docker存储概念 1.容器本地存储与Docke存储驱动 容器本地存储:每个容器都被自动分配了内部存储,即容器本地存储.采用的是联合文件系统.通过存储驱动进行管理. 存储驱动:控制镜像和容器在 ...
- webgl(three.js)实现室内三维定位,3D定位,3D楼宇bim、实时定位三维可视化解决方案——第十四课(定位升级版)
序: 还是要抽出时间看书的,迷上了豆豆的作品,最近在看<天幕红尘>,书中主人公的人生价值观以及修为都是让我惊为叹止.很想成为那样的人,但是再看看自己每天干的事,与时间的支配情况,真是十分的 ...
- KingbaseES XA 分布式事务
在分布式系统中,各个节点(或者事务参与方)之间在物理上相互独立,各节点之间无法确切地知道其它节点中的事务执行情况,所以多节点之间很难保证ACID,尤其是原子性.如果是单节点的事务,由于存在事务机制,可 ...
- KingbaseES R3 受限dba影响集群切换
一.受限dba功能说明(参考自官方文档) 受限DBA 受限DBA可以对当前DBA的权限进行一定限制.当功能开启后DBA将不能更改以下对象: Table Database Function(by n ...
- 如何使用memstat 插件分析内存泄漏问题
对于内存泄漏问题,如何分析并找到内存泄漏的原因是个难点.KingbaseES 提供了memstat 扩展插件用于分析内存泄漏的原因. 一.使用 memstat 插件 1.修改shared_preloa ...
- flink-cdc同步mysql数据到kafka
本文首发于我的个人博客网站 等待下一个秋-Flink 什么是CDC? CDC是(Change Data Capture 变更数据获取)的简称.核心思想是,监测并捕获数据库的变动(包括数据 或 数据表的 ...
- ProxySQL(1):简介和安装
文章转载自:https://www.cnblogs.com/f-ck-need-u/p/9278818.html ProxySQL有两个版本:官方版和percona版,percona版是在官方版的基础 ...