写在开头

随手一翻,发现对于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. SpringMVC简介 & 原理

    特点 1.轻量级,简单易学 2.高效,基于请求响应的MVC框架 3.与Spring兼容性好,与之无缝接合(就是它的一部分) 4.约定优于配置(maven) 5.功能强大:支持RESTful  数据验证 ...

  2. 引领文旅新体验!3DCAT实时云渲染助力打造“永不落幕”的湾区文采会元宇宙

    2022年11月25日至27日,2022年粤港澳大湾区公共文化和旅游产品(东莞)采购会(简称"湾区文采会")在广东省东莞市文化馆举行. 文采会期间,文采会元宇宙线上虚拟展厅全新亮相 ...

  3. Clang Preprocessor 类的创建

    参考: Create a working compiler with the LLVM framework, Part 2 How to parse C programs with Clang: A ...

  4. 如何使用LOTO示波器 绘制 频率响应特性曲线?

    在工作和项目中,经常会遇到一个功能电路模块对信号进行调理,或滤波,或放大,或衰减,或阻抗变换.这些功能电路模块可能是无源阻容的,也可能是有源的运放电路,也可能是更复杂的系统.但是它们对信号进行调理的最 ...

  5. CDN 引入 axios 和 qs 及其使用方法

    一些小项目,没必要搭建脚手架,直接以CDN的方式引入 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&quo ...

  6. FPGA中的速度优化

    FPGA中的速度优化 一.逻辑设计中的速度概念 逻辑设计速度相关的概念有三个:设计吞吐量.设计延时和设计时序.速度优化策略而言,吞吐量需要提高,延时应该降低,时序应该收敛(时序余量slave越大,收敛 ...

  7. 学生成绩管理--C语言

    # 学生成绩管理系统 效果 1.菜单选项 void welcome()//菜单 { printf("欢迎使用学生管理系统\n"); printf(" 1.增加学生信息\n ...

  8. 算法学习笔记【1】| LCA(最近公共祖先)

    LCA(最近公共祖先) Part 1:逐步上跳 假设u,v是所求的两点,先把深度大的点逐步上移直到深度相同. 然后两者同时上移,直到第一次遇到相同的点为止. 时间复杂度: O(n)<script ...

  9. IPv4地址的结构体与网络字节序

    IPv4地址的结构体 /* Fixed-size types, underlying types depend on word size and compiler. */ typedef signed ...

  10. 理解持续测试,才算理解DevOps

    软件产品的成功与否,在很大程度上取决于对市场需求的及时把控,采用DevOps可以加快产品交付速度,改善用户体验,从而有助于保持领先于竞争对手的优势. 作为敏捷开发方法论的一种扩展,DevOps强调开发 ...