Java Synchronized 和 ReentrantLock

 目录

介绍

synchronized

synchronized 修饰实例方法

修饰静态类方法

synchronized 修饰代码块

实现细节

ReentrantLock

ReentrantLock 基本使用

公平锁实现

读写锁(ReentrantReadWriteLock)

1. 创建读写锁对象:

2. 通过读写锁对象分别获取读锁(ReadLock)和写锁(WriteLock):

总结


介绍

        synchronized 关键字相信每一位 Java 工程师都不会陌生。而 ReentrantLock 作为大神 Doug Lea 编写的 concurrent 包中的重要一员,也在众多项目中发挥重要功能。因为这两者实在是太重要,所以专门使用一课时的内容来对它们做一个详细的比较。后续两课时将会着重介绍它们各自的实现细节与原理。

synchronized

synchronized 可以用来修饰以下 3 个层面:

  1. 修饰实例方法;

  2. 修饰静态类方法;

  3. 修饰代码块。

synchronized 修饰实例方法

        这种情况下的锁对象是当前实例对象,因此只有同一个实例对象调用此方法才会产生互斥效果,不同实例对象之间不会有互斥效果。比如如下代码:

        上述代码,在不同的线程中调用的是不同对象的 printLog 方法,因此彼此之间不会有排斥。运行效果如下:

可以看出,两个线程是交互执行的。

        如果将代码进行如下修改,两个线程调用同一个对象的 printLog 方法:

        则执行效果如下:

        可以看出:只有某一个线程中的代码执行完之后,才会调用另一个线程中的代码。也就是说此时两个线程间是互斥的。

修饰静态类方法

        如果 synchronized 修饰的是静态方法,则锁对象是当前类的 Class 对象。因此即使在不同线程中调用不同实例对象,也会有互斥效果。

        将 LagouSynchronizedMehtods 中的 printLog 修改为静态方法,如下:

执行后的打印效果如下:

        可以看出,两个线程还是依次执行的。

synchronized 修饰代码块

        除了直接修饰方法之外,synchronized 还可以作用于代码块,如下代码所示:

        synchronized 作用于代码块时,锁对象就是跟在后面括号中的对象。上图中可以看出任何 Object 对象都可以当作锁对象。

实现细节

        synchronized 既可以作用于方法,也可以作用于某一代码块。但在实现上是有区别的。 比如如下代码,使用 synchronized 作用于代码块:

        使用 javap 查看上述 test1 方法的字节码,可以看出,编译而成的字节码中会包含 monitorenter 和 monitorexit 这两个字节码指令。如下所示:

        你可能已经发现了,上面字节码中有 1 个 monitorenter 和 2 个 monitorexit。这是因为虚拟机需要保证当异常发生时也能释放锁。因此 2 个 monitorexit 一个是代码正常执行结束后释放锁,一个是在代码执行异常时释放锁。

        再看下 synchronized 修饰方法有哪些区别:

        从图中可以看出,被 synchronized 修饰的方法在被编译为字节码后,在方法的 flags 属性中会被标记为 ACC_SYNCHRONIZED 标志。当虚拟机访问一个被标记为 ACC_SYNCHRONIZED 的方法时,会自动在方法的开始和结束(或异常)位置添加 monitorenter 和 monitorexit 指令。

        关于 monitorenter 和 monitorexit,可以理解为一把具体的锁。在这个锁中保存着两个比较重要的属性:计数器和指针。

        计数器代表当前线程一共访问了几次这把锁;

        指针指向持有这把锁的线程。

        用一张图表示如下:

        锁计数器默认为0,当执行monitorenter指令时,如锁计数器值为0 说明这把锁并没有被其它线程持有。那么这个线程会将计数器加1,并将锁中的指针指向自己。当执行monitorexit指令时,会将计数器减1。

ReentrantLock

ReentrantLock 基本使用

        ReentrantLock 的使用同 synchronized 有点不同,它的加锁和解锁操作都需要手动完成,如下所示:

        图中 lock() 和 unlock() 分别是加锁和解锁操作。运行效果如下:

        可以看出,使用 ReentrantLock 也能实现同 synchronized 相同的效果。

        在上面 ReentrantLock 的使用中,将 unlock 操作放在 finally 代码块中。这是因为 ReentrantLock 与 synchronized 不同,当异常发生时 synchronized 会自动释放锁,但是 ReentrantLock 并不会自动释放锁。因此好的方式是将 unlock 操作放在 finally 代码块中,保证任何时候锁都能够被正常释放掉。

公平锁实现

ReentrantLock 有一个带参数的构造器,如下:

        默认情况下,synchronized 和 ReentrantLock 都是非公平锁。但是 ReentrantLock 可以通过传入 true 来创建一个公平锁。所谓公平锁就是通过同步队列来实现多个线程按照申请锁的顺序获取锁。

        公平锁使用实例如下:

        运行效果如下:

可以看出,创建的 3 个线程依次按照顺序去修改 sharedNumber 的值。

        网上对公平锁有一段例子很经典:假设有一口水井,有管理员看守,管理员有一把锁,只有拿到锁的人才能够打水,打完水要把锁还给管理员。每个过来打水的人都要得到管理员的允许并拿到锁之后才能去打水,如果前面有人正在打水,那么这个想要打水的人就必须排队。管理员会查看下一个要去打水的人是不是队伍里排最前面的人,如果是的话,才会给你锁让你去打水;如果你不是排第一的人,就必须去队尾排队,这就是公平锁。

读写锁(ReentrantReadWriteLock)

        在常见的开发中,我们经常会定义一个线程间共享的用作缓存的数据结构,比如一个较大的 Map。缓存中保存了全部的城市 Id 和城市 name 对应关系。这个大 Map 绝大部分时间提供读服务(根据城市 Id 查询城市名称等)。而写操作占有的时间很少,通常是在服务启动时初始化,然后可以每隔一定时间再刷新缓存的数据。但是写操作开始到结束之间,不能再有其他读操作进来,并且写操作完成之后的更新数据需要对后续的读操作可见。

        在没有读写锁支持的时候,如果想要完成上述工作就需要使用 Java 的等待通知机制,就是当写操作开始时,所有晚于写操作的读操作均会进入等待状态,只有写操作完成并进行通知之后,所有等待的读操作才能继续执行。这样做的目的是使读操作能读取到正确的数据,不会出现脏读。

        但是如果使用 concurrent 包中的读写锁(ReentrantReadWriteLock)实现上述功能,就只需要在读操作时获取读锁,写操作时获取写锁即可。当写锁被获取到时,后续的读写锁都会被阻塞,写锁释放之后,所有操作继续执行,这种编程方式相对于使用等待通知机制的实现方式而言,变得简单明了。

接下来,我们看下读写锁(ReentrantReadWriteLock)如何使用:

1. 创建读写锁对象:

2. 通过读写锁对象分别获取读锁(ReadLock)和写锁(WriteLock):

  1. 使用读锁(ReadLock)同步缓存的读操作,使用写锁(WriteLock)同步缓存的写操作:

具体实现,参考如下代码片段:

解释说明:

  • 图中的 number 是线程中共享的数据,用来模拟缓存数据;

  • 图中①处,分别创建 2 个 Reader 线程并从缓存中读取数据,和 1 个 Writer 将数据写入缓存中;

  • 图中②处,使用读锁(ReadLock)将读取数据的操作加锁;

  • 图中③处,使用写锁(WriteLock)将写入数据到缓存中的操作加锁。

上述代码执行效果如下:

        仔细查看日志,可以看出当写入操作在执行时,读取数据的操作会被阻塞。当写入操作执行成功后,读取数据的操作继续执行,并且读取的数据也是最新写入后的实时数据。

总结

        这课时我们主要学习了 Java 中两个实现同步的方式 synchronized 和 ReentrantLock。其中 synchronized 使用更简单,加锁和释放锁都是由虚拟机自动完成,而 ReentrantLock 需要开发者手动去完成。但是很显然 ReentrantLock 的使用场景更多,公平锁还有读写锁都可以在复杂场景中发挥重要作用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IDS 和 IPS:了解异同

IDS 和 IPS 是至关重要的网络安全技术,经常被混淆或互换使用。那么,IDS 和 IPS 之间有什么区别,哪一种是最适合您组织需求的选择呢? 什么是IDS(入侵检测系统)? 入侵检测系统 (IDS) 是一种网络…

【管理篇 / 登录】❀ 06. macOS下使用USB配置线登录 ❀ FortiGate 防火墙

【简介】飞塔防火墙上都会配有CONSOLE接口,包装里都会配置一根USB配置线,通过这个接口和这根线,我们可以用命令的方式登录飞塔防火墙进行操作。随着苹果电脑的普及,我们来学习一下,如果在MAC OS中用配置线登录飞塔防火…

【零基础入门TypeScript】判断条件和循环

目录 定环 无限循环 示例:while 与 do..while 中断语句 句法 流程图 例子 继续语句 句法 流程图 例子 输出 无限循环 语法:使用 for 循环的无限循环 示例:使用 for 循环的无限循环 语法:使用 while 循环进行无限循…

pytorch06:权重初始化

目录 一、梯度消失和梯度爆炸1.1相关概念1.2 代码实现1.3 实验结果1.4 方差计算1.5 标准差计算1.6 控制网络层输出标准差为11.7 带有激活函数的权重初始化 二、Xavier方法与Kaiming方法2.1 Xavier初始化2.2 Kaiming初始化2.3 常见的初始化方法 三、nn.init.calculate_gain 一、…

程序媛的mac修炼手册--MacOS系统更新升级史

啊,我这个口罩三年从未感染过新冠的天选免疫王,却被支原体击倒😷大意了,前几天去医院体检,刚检查完出医院就摘口罩了🤦大伙儿还是要注意戴口罩,保重身体啊!身体欠恙,就闲…

Excel 插件:ASAP Utilities Crack

ASAP Utilities是一款功能强大的 Excel 插件,填补了 Excel 的空白。在过去的 20 年里,我们的加载项已经发展成为世界上最受欢迎的 Microsoft Excel 加载项之一。 ASAP Utilities 中的功能数量(300 多个)可能看起来有点令人眼花缭乱…

Navicat 技术干货 | 聚合查询的介绍

基础 SQL 查询可以检索、插入、更新和删除记录,而聚合查询可通过提供求和、平均值或最大/最小值等的大型结果集,将数据库交互提升到一个新的水平。本文中,我们将探索聚合 SQL 查询的基础知识,并研究如何有效的利用他们来分析和汇总…

Unity中URP下的线性雾

文章目录 前言一、线性雾 雾效因子二、MixFog1、ComputeFogIntensity 雾效强度计算2、雾效颜色混合 lerp(fogColor, fragColor, fogIntensity); 前言 在之前的文章中,我们实现了URP下的雾效支持。 Unity中URP下的添加雾效支持 在上一篇文章中,我们解析了 URP 下统…

HCIA-Datacom题库(自己整理分类的)——其他网络协议【完】

(一)单选 下列属于链路状态协议的是? Direct static FTP OSPF 解析: FTP:文件传输协议 OSPF:链路状态路由协议 如下图所示的网络主机A通过Telnet登录到路由器A然后在远程的界面通过FTP获取路由器的配置文件&…

【已解决】Invalid bound statement (not found)

报错讯息 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.casey.mapper.SysRoleMapper.getUserRoleCode at org.apache.ibatis.binding.MapperMethod S q l C o m m a n d . < i n i t > ( M a p p e r M e t h o d . j a v a :…

iec104和iec61850

iec104和iec61850 IEC104 规约详细解读(一) 协议结构 IEC104 规约详细解读(二)交互流程以及协议解析 61850开发知识总结与分享【1】 Get the necesarry projects next to each other in the same directory; $ git clone https://github.com/robidev/iec61850_open_server.g…

【GoLang入门教程】Go语言几种标准库介绍(四)

编程语言的未来&#xff1f; 文章目录 编程语言的未来&#xff1f;前言几种库fmt库 (格式化操作)关键函数&#xff1a;示例 Go库标准库第三方库示例 html库(HTML 转义及模板系统)主要功能&#xff1a;示例 总结专栏集锦写在最后 前言 上一篇&#xff0c;我们介绍了debug、enco…

Vue2 - diff 原理(动图演示)

目录 1&#xff0c;diffdiff 的时间点 2&#xff0c;_update 函数3&#xff0c;_patch 函数&#xff08;进行 diff&#xff09;3.1&#xff0c;根节点比较3.2&#xff0c;子节点比较 4&#xff0c;key的问题举例1举例2 1&#xff0c;diff 解释&#xff1a;对比新旧虚拟DOM树&a…

很实用的ChatGPT网站—在线编程模块增补篇

很实用的ChatGPT网站&#xff08;http://chat-zh.com/&#xff09;——增补篇 今天介绍一个好兄弟开发的ChatGPT网站&#xff0c;网址[http://chat-zh.com/]。这个网站功能模块很多&#xff0c;包含生活、学习、医疗、法律、经济等很多方面。今天跟大家分享一下&#xff0c;新…

探索模块化神经网络在现代人工智能中的功效和应用

一、介绍 在快速发展的人工智能领域&#xff0c;模块化神经网络 (MNN) 已成为一项关键创新。与遵循整体方法的传统神经网络架构不同&#xff0c;MNN 采用分散式结构。本文深入探讨了 MNN 的基础知识、它们的优势、应用以及它们带来的挑战。 evertongomede 在人工智能领域&#…

【Java期末】学生成绩管理系统

诚接计算机专业编程任务(C语言、C、Python、Java、HTML、JavaScript、Vue等)10/15R&#xff0c;如有需要请私信我&#xff0c;或者加我的企鹅号&#xff1a;1404293476 本文资源下载地址&#xff1a;https://download.csdn.net/download/weixin_47040861/88697244 —————…

imgaug库指南(一):从入门到精通的【图像增强】之旅

文章目录 引言imgaug简介安装和导入imgaug代码示例imgaug的强大之处和用途小结结尾 引言 在深度学习和计算机视觉的世界里&#xff0c;数据是模型训练的基石&#xff0c;其质量与数量直接影响着模型的性能。然而&#xff0c;获取大量高质量的标注数据往往需要耗费大量的时间和…

Spark---RDD算子(单值类型Value)

文章目录 1.RDD算子介绍2.转换算子2.1 Value类型2.1.1 map2.1.2 mapPartitions2.1.3 mapPartitionsWithIndex2.1.4 flatMap2.1.5 glom2.1.6 groupBy2.1.7 filter2.1.8 sample2.1.9 distinct2.1.10 coalesce2.1.11 repartition2.1.12 sortBy 1.RDD算子介绍 RDD算子是用于对RDD进…

【数据结构】循环队列(数组实现)

目录 一、循环队列定义 怎么使一个数组在逻辑上呈“环状”呢&#xff1f; 二、循环队列与顺序队列的差异 1、存储方式: 2、操作方式: 3、空间利用率&#xff1a; 4、循环队列判断队空的方式&#xff1a; 5、循环队列判断队满的方式 完整测试代码及注释&#xff1a; 总…

Vue 中的 ref 与 reactive:让你的应用更具响应性(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…