写在开头

随手一翻,发现对于Java中并发多线程的学习已经发布了十几篇博客了,多线程 是Java基础中的重中之重!因此,可能还需要十几篇博客才能大致的讲完这部分的知识点,初学者对于这部分内容一定要多花心思,不可马虎!今天我们继续来学习一个重要知识点:ReentrantLock

ReentrantLock :是一种独占式的可重入锁,位于java.util.concurrent.locks中,是Lock接口的默认实现类,底部的同步特性基于AQS实现,和synchronized关键字类似,但更灵活、功能更强大、也是目前实战中使用频率非常高的同步类。

几种不同锁的定义

在学习ReentrantLock之前,我们先来复习一下如下的几类锁的定义,这个其实很早的博文中就已经详细的整理过了,这里为了更好理解ReentrantLock锁,还是简单罗列一下。

独占锁与共享锁

  1. 独占锁:同一时间,一把锁只能被一个线程获取;
  2. 共享锁:同意时间,一把锁可以被多个线程获取。

公平锁与非公平锁

  1. 公平锁:按照申请锁的时间先后,进行锁的再分配工作,这种锁往往性能稍差,因为要保证申请时间上的顺序性;
  2. 非公平锁: 锁被释放后,后续线程获得锁的可能性随机,或者按照设置的优先级进行抢占式获取锁。

可重入锁

所谓可重入锁就是一个线程在获取到了一个对象锁后,线程内部再次获取该锁,依旧可以获得,即便持有的锁还没释放,仍然可以获得,不可重入锁这种情况下会发生死锁!

可重入锁在使用时需要注意的是:由于锁会被获取 n 次,那么只有锁在被释放同样的 n 次之后,该锁才算是完全释放成功。

可中断锁与不可中断锁

  1. 可中断锁:在获取锁的过程中可以中断获取,不需要非得等到获取锁后再去执行其他逻辑;
  2. 不可中断锁:一旦线程申请了锁,就必须等待获取锁后方能执行其他的逻辑处理。

ReentrantLock是一种同时拥有独占式、可重入、可中断、公平/非公平特性的同步器!


ReentrantLock

根据上面总结出的特点,我们从底层源码出发来验证一下结论的准确性,首先我们通过一个关系图谱来大致梳理一下ReentrantLock的内部构造。

ReentrantLock实现了Lock和Serializable接口:

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

其内部拥有三个内部类,分别为Sync、FairSync、NonfariSync,其中FairSync、NonfariSync继承父类Sync。Sync又继承了AQS(AbstractQueuedSynchronizer),添加锁和释放锁的大部分操作实际上都是在 Sync 中实现的。

问题1:ReentrantLock内部公平锁与非公平锁如何实现?

在内部通过构造器来实现公平锁与非公平锁的设置,默认为非公平锁,同样可以通过传参设置为公平锁。底层实现其实是通过FairSync、NonfariSync这个两个内部类,源码如下:

//无参构造,默认为非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 传入一个 boolean 值,true 时为公平锁,false 时为非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

问题2:独占锁如何实现?

在源码中无论是Sync这个内部类或是其子类,都会调用setExclusiveOwnerThread(current)这个方法,这个方法是AQS的父类AOS(AbstractOwnableSynchronizer)中的方法,用以标记锁的持有者为独占模式。

问题3:ReentrantLock如何获取和释放锁?

由于ReentrantLock是默认非公平锁,所以我们就以非公平模式为例去看一下它底层如何实现锁的获取与释放的。

1️⃣ 锁的获取

核心方法为Sync内部类的nonfairTryAcquire方法,如下为其源码,先获取当前锁的状态,若为0说明没有被任何线程获取,此时直接获取即可;另外一种state不为0时,则需要判断占有线程是否为当前线程,若是则可以获取,并将state值加一返回,否则获取失败。

【注意】:公平模式下获取锁的时会多一步调用hasQueuedPredecessors 的逻辑判断,用以判断当前线程对应的节点在等待队列中是否有前驱节点,毕竟公平锁的竞争严格按照获取锁的时间进行分配的。

final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//1. 如果该锁未被任何线程占有,该锁能被当前线程获取
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//2.若被占有,检查占有线程是否是当前线程
else if (current == getExclusiveOwnerThread()) {
// 3. 再次获取,计数加一
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

2️⃣ 锁的释放

对应的以非公平锁中释放为例,通过源码我们可以看到,每调用一次则同步状态减1,直至同步状态为0,锁才被完全的释放完,否则返回false。

protected final boolean tryRelease(int releases) {
//1. 同步状态减1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//2. 只有当同步状态为0时,锁成功被释放,返回true
free = true;
setExclusiveOwnerThread(null);
}
// 3. 锁未被完全释放,返回false
setState(c);
return free;
}

3️⃣ 小总结

经过上面源码的学习,我们已经能够确认一点就是:ReentrantLock是一种同时拥有独占式、可重入、可中断、公平/非公平特性的同步器!我们接下来就继续再来学习一下它的使用。

问题4:ReentrantLock的使用

我们通过一个小demo,来感受一下基于非公平锁模式下的ReentrantLock的使用哈

public class Test {
//初始化一个静态lock对象
private static final ReentrantLock lock = new ReentrantLock();
//初始化计算量值
private static int count; public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i <1000 ; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 1000; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("result:"+count);
}
}

上面这个程序预期输出结果为:2000,thread1和thread2分别做了加1000次的操作,由于ReentrantLock是独占式可重入锁,故最终可以成功打印出预期结果!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

从源码入手详解ReentrantLock,一个比synchronized更强大的可重入锁的更多相关文章

  1. NopCommerce源码架构详解--初识高性能的开源商城系统cms

    很多人都说通过阅读.学习大神们高质量的代码是提高自己技术能力最快的方式之一.我觉得通过阅读NopCommerce的源码,可以从中学习很多企业系统.软件开发的规范和一些新的技术.技巧,可以快速地提高我们 ...

  2. NopCommerce源码架构详解

    NopCommerce源码架构详解--初识高性能的开源商城系统cms   很多人都说通过阅读.学习大神们高质量的代码是提高自己技术能力最快的方式之一.我觉得通过阅读NopCommerce的源码,可以从 ...

  3. Nop--NopCommerce源码架构详解专题目录

    最近在研究外国优秀的ASP.NET mvc电子商务网站系统NopCommerce源码架构.这个系统无论是代码组织结构.思想及分层都值得我们学习.对于没有一定开发经验的人要完全搞懂这个源码还是有一定的难 ...

  4. Hadoop3.1.1源码Client详解 : 写入准备-RPC调用与流的建立

    该系列总览: Hadoop3.1.1架构体系——设计原理阐述与Client源码图文详解 : 总览 关于RPC(Remote Procedure Call),如果没有概念,可以参考一下RMI(Remot ...

  5. Hadoop3.1.1源码Client详解 : 入队前数据写入

    该系列总览: Hadoop3.1.1架构体系——设计原理阐述与Client源码图文详解 : 总览 紧接着上一篇: Hadoop3.1.1源码Client详解 : 写入准备-RPC调用与流的建立 先给出 ...

  6. Hadoop3.1.1源码Client详解 : Packet入队后消息系统运作之DataStreamer(Packet发送) : 主干

    该系列总览: Hadoop3.1.1架构体系——设计原理阐述与Client源码图文详解 : 总览 在上一章(Hadoop3.1.1源码Client详解 : 写入准备-RPC调用与流的建立) 我们提到, ...

  7. Hadoop3.1.1源码Client详解 : Packet入队后消息系统运作之ResponseProcessor(ACK接收)

    该系列总览: Hadoop3.1.1架构体系——设计原理阐述与Client源码图文详解 : 总览 紧接着上一篇文章: Hadoop3.1.1源码Client详解 : Packet入队后消息系统运作之D ...

  8. Hadoop3.1.1架构体系——设计原理阐述与Client源码图文详解 : 总览

    一.设计原理 1.Hadoop架构: 流水线(PipeLine) 2.Hadoop架构: HDFS中数据块的状态及其切换过程,GS与BGS 3.Hadoop架构: 关于Recovery (Lease ...

  9. Flask源码剖析详解

    1. 前言 本文将基于flask 0.1版本(git checkout 8605cc3)来分析flask的实现,试图理清flask中的一些概念,加深读者对flask的理解,提高对flask的认识.从而 ...

  10. linux 基础入门(8) 软件安装 rpm、yum与源码安装详解

    8.软件 RPM包安装 8.1rpm安装 rpm[选项]软件包名称 主选项 -i 安装 -e卸载 -U升级 -q查找 辅助选项 -ⅴ显示过程 -h --hash 查询 -a-all查询所有安装的包 - ...

随机推荐

  1. gitee 流水线 定时触发 不能用,不能白嫖了

    gitee 流水线 定时触发 不能用,不能白嫖了 白研究半天了,只好回去拿centos服务器 搞定时任务了

  2. CloudXR技术如何运用于农业?

    随着科技的不断发展和应用的深入,农业领域也在逐渐引入新技术来优化生产效率和成本.改进管理和监控等.云化XR(CloudXR)作为一种融合了云计算.虚拟现实(VR)和增强现实(AR)等技术的解决方案,也 ...

  3. 3DCAT+上汽奥迪:打造新零售汽车配置器实时云渲染解决方案

    在 5G.云计算等技术飞速发展的加持下,云渲染技术迎来了突飞猛进的发展.在这样的背景下,3DCAT应运而生,成为了业内知名的实时云渲染服务商之一. 交互式3D实时云看车作为云渲染技术的一种使用场景,也 ...

  4. 3DCAT实时渲染云在虚拟展会中的应用

    随着互联网技术的不断发展,实时3D可视化技术在日常生活中应用越来越广泛,越来越多的行业开始转向线上.今年受新冠肺炎疫情影响很多展会都无法在线下举办,而3d线上虚拟展会采用了全新的在线展示产品方式,将展 ...

  5. iOS Modern Collection View

    TL;DR 使用的技术: Compositional layout + Diffable data source.iOS 14+. 创建 layout 以描述布局: 创建 dataSource 以提供 ...

  6. C++关于栈对象返回的问题

    本次实验环境 环境1:Win10, QT 5.12 环境2:Centos7,g++ 4.8.5 一. 主要结论 可以返回栈上的对象(各平台会有不同的优化),不可以返回栈对象的引用. 二.先看看函数传参 ...

  7. 容器镜像加速指南:探索 Kubernetes 缓存最佳实践

    介绍 将容器化应用程序部署到 Kubernetes 集群时,由于从 registry 中提取必要的容器镜像需要时间,因此可能会出现延迟.在应用程序需要横向扩展或处理高速实时数据的情况下,这种延迟尤其容 ...

  8. 【Jenkins】Jenkins 运行权限问题

    yum安装的Jenkins 配置文件默认位置/etc/sysconfig/jenkins 默认jenkins服务以jenkins用户运行,这时在jenkins执行maven脚本时可能会发生没有权限操作 ...

  9. linux关闭主板警告声,蜂鸣声,滴滴声,pc扬声器。

    启动时,BIOS 通常会在开机自检期间发出蜂鸣声.较新的主板型号省略了开机自检蜂鸣声,以便快速启动进入操作系统.BIOS 通常允许切换开机自检蜂鸣声,但无法将 PC 扬声器配置为完全关闭.一旦系统启动 ...

  10. #线段树,组合计数,二项式定理#CF266E More Queries to Array

    洛谷传送门 CF266E传送门 分析 首先区间修改区间查询首选线段树 要找突破口,\((i-l+1)^k\)中\(i\)不是定值, 显然得拆开,而且\(k\)很小,根据二项式定理, \[\sum_{i ...