彻底理解Java并发:ReentrantLock锁

news2024/9/21 10:37:28

本篇内容包括:为什么使用 Lock、Lock 锁注意事项、ReentrantLock 和 synchronized 对比、ReentrantLock (加锁、解锁、公平锁与非公平锁、ReentrantLock 如何实现可重入)等内容。

一、Lock 锁

1、为什么使用 Lock

synchronized 线程等待时间过长,获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,这将极大的影响程序执行效率。

synchronized 操作场景,如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

2、注意事项

也就是说 Lock 提供了比 synchronized 更多的功能。但是要注意以下几点

  • Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问;
  • Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

3、ReentrantLock 和 synchronized

ReentrantLock 是 java.util.concurrent.locks 包中的一个类,是独占锁,为最后一个执行 lock 操作成功且为释放锁的线程锁拥有

ReentrantLock 是可重入的互斥锁,虽然具有与 synchronized 相同功能,但是会比 synchronized 更加灵活

ReentrantLock 使用代码实现了和 synchronized 一样的语义,包括可重入,保证内存可见性和解决竞态条件问题等。与 synchronized 相较之下:

  • 便利性:Synchronized 用法更简洁,由编译器去保证锁的加锁和释放; ReenTrantLock 需要手动加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在 finally 中声明释放锁。
  • 锁的细粒度和灵活度:ReenTrantLock 优于 Synchronized

此外,以下特点是 ReenTrantLock 独有:

  • ReenTrantLock 可以指定是公平锁还是非公平锁; synchronized 只能是非公平锁。
  • ReenTrantLock 提供了一个 Condition 类,用来实现唤醒特定的线程; synchronized 要么随机唤醒一个线程要么唤醒全部线程。
  • ReenTrantLock 提供了一种能够中断等待锁的线程的机制。

二、ReentrantLock

ReentrantLock,它是一个“可重入”锁。

什么是“可重入”?简单地讲就是:“同一个线程对于已经获得到的锁,可以多次继续申请到该锁的使用权”

正经地讲就是:假如访问一个资源A需要获得其锁lock,如果之前没有其他线程获取该锁,那么当前线程就获锁成功,此时该线程对该锁后续所有“请求”都将立即得到“获锁成功”的返回,即同一个线程可以多次成功的获取到之前获得的锁。“可重入”可以解释成“同一个线程可多次获取”。

大致的特性

  • 基本锁的特性:加锁、解锁

  • ReentrantLock的补充特性:可重入、公平、非公平

1、加锁、解锁

这两个方法在源码中加锁方法即为lock(),解锁方法即为unLock() ,实现如下:

//加锁
public void lock() {
    sync.lock();
}

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

从上述可以知道这两个方法实际上是操作了一个叫做 sync 的对象,调用该对象的 lock 和 release 操作来实现,sync 是什么东西?ReentrantLock 类的源码片段:

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    private final Sync sync;
}

ReentrantLock 实现了 Lock 接口,操作其成员变量 sync 这个 AQS 的子类,来完成锁的相关功能。而 sync 这个成员变量有2种形态:NonfairSync 和 FairSync,在源码中,只有在2个构造函数的地方对sync对象做了初始化

/** 所有锁操作都是基于这个字段 */
private final Sync sync;
/**
 * 通过该构造函数创建额ReentrantLock是一个非公平锁
 */
public ReentrantLock() {
    sync = new NonfairSync();
}
/**
 * 如果入参为true,则创建公平的ReentrantLock;
 * 否则,创建非公平锁
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

这两个对象(NonfairSync和NonfairSync)也是 ReentrantLock 的内部类,FairSync 和 NonFairSync 在类结构上完全一样且均继承于 Sync。

img

ReentrantLock的构造函数中,默认的无参构造函数将会把Sync对象创建为NonfairSync对象,这是一个“非公平锁”;而另一个构造函数ReentrantLock(boolean fair)传入参数为true时将会把Sync对象创建为“公平锁”FairSync

2、公平锁与非公平锁

img

FairSync 在 tryAquire 方法中,当判断到锁状态字段state == 0 时,不会立马将当前线程设置为该锁的占用线程,而是去判断是在此线程之前是否有其他线程在等待这个锁(执行hasQueuedPredecessors() 方法),如果是的话,则该线程会加入到等待队列中,进行排队(FIFO,先进先出的排队形式)。这也就是为什么 FairSync 可以让线程之间公平获得该锁。

NoFairSync的tryAquire 方法中,没有判断是否有在此之前的排队线程,而是直接进行获锁操作,因此多个线程之间同时争用一把锁的时候,谁先获取到就变得随机了,很有可能线程A比线程B更早等待这把锁,但是B却获取到了锁,A继续等待(这种现象叫做:线程饥饿)

到此,我们已经大致理解了 ReentrantLock 是如何做到不同线程如何“公平”和“非公平”获锁。

3、如何实现可重入

我们有提到加锁操作会对 state 字段进行 +1 操作

这里需要注意到 AQS 中很多内部变量的修饰符都是采用的 volital,然后配合 CAS 操作来保证 AQS 本身的线程安全(因为 AQS 自己线程安全,基于它的衍生类才能更好地保证线程安全),这里的 state 字段就是 AQS 类中的一个用 volitale 修饰的 int 变量

state 字段初始化时,值为 0。表示目前没有任何线程持有该锁。当一个线程每次获得该锁时,值就会在原来的基础上加 1,多次获锁就会多次加 1(指同一个线程),这里就是可重入。因为可以同一个线程多次获锁,只是对这个字段的值在原来基础上加1; 相反 unlock 操作也就是解锁操作,实际是是调用 AQS 的 release 操作,而每执行一次这个操作,就会对 state 字段在原来的基础上减1,当 state==0 的 时候就表示当前线程已经完全释放了该锁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1999.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Filter快速入门、Filter执行流程、Filter使用细节、Listener概念、分类、ServletContextListener使用

文章目录FilterFilter快速入门Filter 执行流程Filter使用细节ListenerServletContextListener 使用Filter 概念:Filter表示过滤器,是 JavaWeb三大组件(Servlet、Filter、Listener)之一。过滤器可以把对资源的请求拦截下来&#x…

DASCTF X GFCTF 2022十月挑战赛web

前言 晚来的比赛web题解,这次buu的十月赛web部分的题目对于我来说质量还是蛮高的,因为这几天比较忙,一直没有去复现总结,不过该复现的还得复现,复现了这次比赛又能学到不少知识,嘿嘿嘿。 EasyPOP 考察ph…

Mega-Nerf学习笔记

Mega-NeRF:Scalable Construction of Large-Scale NeRFs for Virtual Fly-Throughs 主页:https://meganerf.cmusatyalab.org/ 论文:https://meganerf.cmusatyalab.org/resources/paper.pdf 代码:https://github.com/cmusatyalab/mega-nerf …

【设计模式】简单工厂模式

简单工厂模式–》工厂模式—》抽象工厂模式 文章目录简单工厂模式定义:各个角色1. 抽象产品类2. 具体产品类:3. 工厂类:简单工厂模式的核心。客户端设计图表未使用简单工厂模式:出现的问题:使用简单工厂模式&#xff1…

小熊派-FreeRTOS-点灯学习过程-20221029

一、前言准备 1、小熊派一个(STM32L431RCT6) 2、STM32CubeMX 3、keil5 4、小熊派的配套开发资料(用于出问题的时候替换) 二、实现过程 代码主要由STM32CubeMX生成,所以过程主要是配置CubeMX. 1、芯片选型 STM3…

用 Pyinstaller 模块将 Python 程序打包成 exe 文件(全网最全面最详细)

目录 打包前置知识 一、什么是exe可执行文件? 二、为什么要将 Python 程序打包为 exe 可执行文件? 三、为什么 Python 程序不能直接运行呢? 四、我们用什么来打包 Python 文件呢? 五、打包有哪几种分类呢? 打包…

【ArcGIS微课1000例】0041:ArcGIS利用坐标生成点的方法总结

本文讲解ArcGIS利用坐标生成点的3种方法。 文章目录 一、转到XY工具定位二、输入绝对XY生成点三、添加XY数据一、转到XY工具定位 这样确实可以在图上快速定位某个经纬度的点,但是生成的对象是“注记类”要素,即不是地理实体,而仅仅是为了绘图表现的东西。可以用如下工具来控…

Kotlin协程-并发处理-基础

一、协程与并发 Kotlin协程是基于线程执行的。经过一层封装以后,Kotlin协程面对并发,处理方式与Java不同。 在java的世界里,并发往往是多个线程一起工作,存在共享的变量。需要处理好同步问题。要避免把协程与线程的概念混淆。 …

验证码的编写

编写一个验证码(可以通过键盘输入需要获取验证码的位数): public class IdentifyingCode {public static void main(String[] args) {//验证码的编写IdentifyingCode identifyingCode new IdentifyingCode();//扫描键盘输入Scanner scanner new Scanner(System.i…

【数据结构基础】之数组介绍,生动形象,通俗易懂,算法入门必看

【数据结构基础】数组前言一、数组的定义1.基本概念和内存图2.数组的性质3.数据结构二、玩转数组1.循环遍历打印数组的每一个值2.查找一个数组里存在的值3.打擂台的形式找最大值4.元素的位移5.数组的扩容6.数组的反转三、二维数组四、初识算法1.排序算法2.查找算法3.算法性能4.…

电力电子的一些知识

文章目录数电模电逻辑电路与或非异或 门电路与的物理电路边沿触发器功率交流容量直流容量桥电路CHBDABTHD电路器件LM7815与LM7915数电模电 逻辑电路 与或非异或 门电路 与乘大于1或加大于1异或异性为1,异吗? 与的物理电路 当二极管是高电平&#xf…

stm32f407探索者开发板(一)——资源介绍(顺便说下无人机的进度状况)

文章目录零、前言一、前置问题二、注意事项三、学习方法四、外观五、关于sw仿真零、前言 最近虽然在做无人机小项目啦,但是呢由于疫情,各种零件一直没发,很头疼,现在关于遥控器和接收机的选型也没定下来,嗯&#xff0…

【Java】Spring boot快速上手(一):葵花宝典

目录前言学习目标学习内容工具及其环境配置说明新建spring web项目打开idea 新建项目创建项目配置设置静态资源映射编写html访问前端最后前言 该系列文章仅用于个人学习记录,适合入门级,对于文中有错误的地方还望海涵,之前打算结合《瑞吉外卖…

【JavaSE】初识Java

文章目录一. Java语言概述1. Java语言的组成1.1 什么是JavaSE1.2 什么是JavaEE1.3 什么是JavaME2. Java语言的产生3. Java语言的优势二. 初识Java的main方法1. main方法示例2. 运行Java程序一. Java语言概述 Java是一门面向对象编程语言,不仅吸收了C语言的各种优点…

学习python第一天,请教一下怎么学?

前言 小白建议以视频入门,或者是看一些适合入门的书比如《笨办法学Python》; 在这个资源泛滥的时代,更需要有自己的一套学习方法。尤其是针对自学来说,遇到问题不知道如何解决,那么就要做好前置的学习计划&#xff1…

我越努力生活,生活就越努力的干我

92年出生,工作9年。我也不知道今年多少岁了,结婚6年,两个小孩,一个是男孩,「该死的」另一个也是男孩,周五晚上,手机屏幕的时间刚过9点50,我小心拉开门缝往里面瞧了一眼后推开房门&am…

C++ Reference: Standard C++ Library reference: C Library: cstring: strchr

C官网参考链接&#xff1a;https://cplusplus.com/reference/cstring/strchr/ 函数 <cstring> strchr const char * strchr ( const char * str, int character ); char * strchr ( char * str, int character ); 定位字符串中第一个出现的字符 返回指向C字符串s…

[前端基础] 浏览器篇

提供基础用法&#xff0c;基础概念引用 MDN、W3C&#xff0c;基础内容做扩展知识&#xff0c;可应对面试&#xff0c;详细原理及应用需要去官网、GitHub 深入学习。 1、常用 BOM 方法 BOM&#xff08;browser object model&#xff09;简称浏览器对象模型&#xff0c;BOM 提供…

【SpringBoot笔记18】SpringBoot实现统一异常处理、统一结果响应、统一参数校验

这篇文章&#xff0c;主要介绍如何利用SpringBoot框架实现统一异常处理、统一结果响应、统一参数校验。 目录 一、SpringBoot统一结果响应 1.1、创建工程 1.2、配置FastJson 1.3、创建ResultEnum枚举 1.4、创建Result实体类 二、SpringBoot统一异常处理 2.1、创建自定义…

【LeetCode 784. 字母大小写全排列】异或进行字母大小写变换

LeetCode784.字母大小写全排列解题方法&#xff1a;回溯用异或进行大小写变换正式解题总代码784.字母大小写全排列 给定一个字符串 s &#xff0c;通过将字符串 s 中的每个字母转变大小写&#xff0c;我们可以获得一个新的字符串。 返回 所有可能得到的字符串集合 。以 任意顺…