写在开头

随手一翻,发现对于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. coast 海岸 单词记忆方法

    coast 海岸 单词记忆方法 coa 扣 想象一个碗扣下去 st站 碗的边和地面的接触面 就是海岸的边 coast 逼近的地方-海岸 coast (n.) - "margin of the ...

  2. 基于英特尔® Gaudi® 2 AI 加速器的文本生成流水线

    随着生成式人工智能 (Generative AI,GenAI) 革命的全面推进,使用 Llama 2 等开源 transformer 模型生成文本已成为新风尚.人工智能爱好者及开发人员正在寻求利用此类 ...

  3. ETL工具-KETTLE教程 实例实战4----转换(值映射、列转行,增加常量、增加序列等)

    附:Kettle实战视频教程,需要的朋友可以看看学习下哈~~ kettle实战第一讲-文件和数据库表的互相转换处理_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili kettle实战第二讲-数据库单 ...

  4. cmd查看环境变量

    1.查看当前所有可用的环境变量:输入 set 即可查看. 2.查看某个环境变量:输入 "set 变量名"即可,比如想查看path变量的值,即输入 set path 3.修改环境变量 ...

  5. Performance Improvements in .NET 8 & 7 & 6 -- Thread【翻译】

    线程 .NET 的最近版本在线程.并行.并发和异步等方面做出了巨大的改进,例如 ThreadPool 的完全重写(在 .NET 6 和 .NET 7 中),异步方法基础设施的完全重写(在 .NET C ...

  6. 【Linux】Git 安装最新版

    # 移除旧版 Git yum remove git # 安装依赖包 yum install curl-devel expat-devel gettext-devel openssl-devel zli ...

  7. Redis集群模式和常用数据结构

    一.Redis 支持三种主要的集群模式 主从复制模式(Master-Slave Replication): 在这种模式下,主节点(Master)负责处理写入操作,而从节点(Slave)则是主节点的副本 ...

  8. Vulnhub靶场--EVILBOX: ONE

    环境配置 靶机连接 攻击者主机IP:192.168.47.130 目标主机IP:192.168.47.131 信息搜集 扫描目标主机,发现目标主机开放了22.80端口 ┌──(kali㉿kali)-[ ...

  9. 说说如何在Vue项目中应用TypeScript?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.前言 与link类似 在VUE项目中应用typescript,我们需要引入一个库vue-property-decorator, 其是基 ...

  10. Linux修改账户密码

    打开终端并登录到要修改密码的账户 输入 passwd 命令,然后 Enter 系统会提示你输入 Current password. 如果是第一次登录或者忘记密码,使用 passwd -d userna ...