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 的使用场景更多,公平锁还有读写锁都可以在复杂场景中发挥重要作用。

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

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

相关文章

LeetCode 2807. 在链表中插入最大公约数

给你一个链表的头 head ,每个结点包含一个整数值。 在相邻结点之间,请你插入一个新的结点,结点值为这两个相邻结点值的 最大公约数 。 请你返回插入之后的链表。 两个数的 最大公约数 是可以被两个数字整除的最大正整数。 示例 1&#xf…

Vue前端文字效果:如何让一段文本像是手动一个一个字打出来的

效果展示 自己做的AI聊天机器人界面,我觉得比微信还好看 由于这个前端略微复杂,下文用最简单的例子来展示: 分析需求 对于AI聊天工具的前端,如果AI生成的文本像是一个一个字打出来的,就会让AI看起来更像真的人&…

MySQL主从复制案例实现

使用Sharding-JDBC实现读写分离&#xff1a; 1、导入maven坐标 2、 在配置文件中配置读写分离规则 3、在配置文件中配置允许bean定义覆盖配置项 1、导入maven坐标 <dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jd…

SoapUI 怎么下载:实用指南

SoapUI Windows 版本下载 今天带大家过一遍 SoapUI 在 Windows 系统下的安装教程吧&#xff01;各位 开发小伙伴 们可以跟着我一起来~ 下载安装包 下载链接&#xff1a;www.soapui.org/downloads/s… 安装 安装非常简单&#xff0c;只需双击它即可启动&#xff0c;安装程序…

AWS(三):如何在AwsManagedAd目录和windowsAD实例之间建立双向信任。

前提&#xff1a; 1.创建好了一个AWS managed AD目录&#xff0c;我的目录域名为:aws.managed.com 2.创建好了一个windows AD实例并提升了为域控服务器,实例域名为:aws2.com 看过我AWS 一和二的应该都会创建windows实例了&#xff0c;切记不能将其无缝加入到aws managed AD的…

Springboot通过profiles切换不同环境使用的配置

文章目录 简介1.通过分隔符隔离2.通过使用不同的配置文件区分3.测试 简介 一个项目从开发到上线一般要经过几个环境, dev测试环境-uat预生产环境-prod生产环境&#xff0c;每个环境的使用的数据库或者配置不同&#xff0c;这时候可以通过下面两种方式区分配置,达到快速切换的效…

密码学:带密钥的消息摘要算法一数字签名算法

文章目录 前言手写签名和数字签名前置知识点&#xff1a;消息摘要算法数字签名算法数字签名算法的由来数字签名算法在实际运用的过程附加&#xff1a;签名和摘要值的解释 数字签名算法的家谱数字签名算法的消息传递模型经典数字签名算法-RSA实现 数字签名标准算法-DSA实现 圆曲…

Grafana UI 入门使用

最近项目上需要使用Grafana来做chart&#xff0c;因为server不是我在搭建&#xff0c;所以就不介绍怎么搭建grafana server&#xff0c;而是谈下怎么在UI上具体操作使用了。 DOCs 首先呢&#xff0c;贴一下官网doc的连接&#xff0c;方便查询 Grafana open source documenta…

大数据StarRocks(三) StarRocks数据表设计

1. 列式存储 1.1 列式存储方式有以下几个优点&#xff1a; 1.快速的数据查询 由于数据是按照列进行存储的&#xff0c;所以查询某个列时只需要读取该列所在的块&#xff0c;而不是整行数据&#xff0c;从而大大提高了查询效率。 2.压缩效率高 由于列式存储的数据块中只有一…

在docekr中运行openwrt镜像

1镜像下载 地址&#xff1a; https://archive.openwrt.org/releases/23.05.1/targets/x86/64/ #linux 下下载命令为 wget https://archive.openwrt.org/releases/23.05.1/targets/x86/64/openwrt-23.05.1-x86-64-rootfs.tar.gz ./#加载镜像 docker import openwrt-23.05.1-x…

计算机毕业设计——基于SSM+Layui的图书管理系统(附源码)

1&#xff0c;项目背景 国家大力推进信息化建设的大背景下&#xff0c;城市网络基础设施和信息化应用水平得到了极大的提高和提高。特别是在经济发达的沿海地区&#xff0c;商业和服务业也比较发达&#xff0c;公众接受新事物的能力和消费水平也比较高。开展商贸流通产业的信息…

HTML5-简单文件操作

文件操作 简介 概念&#xff1a;可以通过file类型的input控件或者拖放的方式选择文件进行操作 语法格式&#xff1a; <input type"file" multiple>属性 multiple&#xff1a;表示是否选择多个文件 accept&#xff1a;用于设置文件的过滤类型&#xff08;MI…

ThreadLocal线程重用导致用户信息错乱的 Bug

在生产上遇到一个诡异的问题&#xff0c;有时获取到的用户信息是别人的。查看代码后&#xff0c;我发现他使用了 ThreadLocal 来缓存获取到的用户信息。 我们知道&#xff0c;ThreadLocal 适用于变量在线程间隔离&#xff0c;而在方法或类间共享的场景。如果用户信息的获取比较…

AI:106-基于卷积神经网络的遥感图像地物分类

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

Linux22.04系统安装显卡驱动,cuda,cudnn流程

1. 安装显卡驱动 ubuntu-drivers deices显示所有适配显卡的驱动型号&#xff0c;recommended为推荐安装 安装 sudo apt install nvidia-driver-440重启 sudo reboot验证 nvidia-smi2. 安装cuda 在 CUDA Toolkit 的下载页面选择系统版本和安装方式&#xff0c;下载并运行…

[Flutter]WindowsOS上运行遇到的问题总结

[Flutter]WindowsOS上运行遇到的问题总结 写在开头 Flutter项目已能在移动端完美使用后&#xff0c;想看看在桌面端等使用情况 基于Flutter3.0后已支持Windows/MacOS等桌面端&#xff0c;不过具体的系统&#xff0c;还需要看下官方文档解释。 这里抛出文档地址&#xff0c;可…

代码随想录刷题笔记(DAY 8)

今日总结&#xff1a;最后一道题解决的比较糟糕&#xff0c;后续会补上新解法&#xff0c;今天还是将中心放在了前端。 Day 8 01. 反转字符串&#xff08;No. 344&#xff09; 题目链接 代码随想录题解 1.1 题目 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。…

医院信息系统集成平台—Ensemble集成平台中间件

Ensemble HIE(健康信息交换)是InterSystems公司一个新的产品,它采用了一种全新的解决方案,是一个强大的应用软件整合平台,它包括了为医疗信息交换预先开发好的组件,使用Ensemble可以快速地整合和开发复合应用程序。Ensemble在增强现有软件功能、协调新的商业过程和集中企…

力扣hot100 二叉树的层序遍历 BFS 队列

&#x1f468;‍&#x1f3eb; 题目地址 时间复杂度&#xff1a; O ( n ) O(n) O(n)空间复杂度&#xff1a; O ( n ) O(n) O(n) &#x1f60b; 队列写法 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode…

Java 第19章 IO流 课堂练习+本章作业

文章目录 Buffered流拷贝二进制文件创建文件写入文本读取文本文件存读Properties文件 Buffered流拷贝二进制文件 package com.hspedu.chapter19.outputStream;import java.io.*;public class BufferedCopy02 {public static void main(String[] args) {String srcFilePath &q…