【六大锁策略-各种锁的对比-Java中的Synchronized锁和ReentrantLock锁的特点分析-以及加锁的合适时机】

news2024/9/25 13:24:15

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 一、六大"有锁策略"
    • 1. 乐观锁——悲观锁
    • 2. 轻量级锁——重量级锁
    • 3. 自旋锁——挂起等待锁
    • 4. 互斥锁——读写锁
    • 5. 可重入锁——不可重入锁
    • 6. 公平锁——非公平锁
  • 二、Synchronized——ReentrantLock
    • Synchronized的特点(JDK1.8)
    • Synchronized的锁升级策略
    • ReentrantLock的特点
    • Synchronized和ReentranLock对比
  • 三、锁消除——锁粗化


前言

阅读该文章之前要了解,锁策略是为了解决什么问题

多线程带来的的风险-线程安全的问题的简单实例-线程不安全的原因


提示:以下是本篇文章正文内容,下面案例可供参考

一、六大"有锁策略"

锁冲突是指两个线程对一个对象加锁,产生了阻塞等待。

1. 乐观锁——悲观锁

乐观锁

  • 假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

  • 预测接下来的锁冲突不大(一般消耗的资源少,效率高点)

悲观锁

  • 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

  • 预测接下来的锁冲突很大(一般消耗的资源多,效率低点)


举个例子

大学里的期末的最后一门考试结束后,当天,辅导员就会通知假期就开始了,大家可以离校了。

  • 同学A乐观锁)认为:反正,每次都是考试完,就可以直接走了,于是他就直接收拾行李,不等通知,直接当时就回家了,出现意外再说。

  • 同学B悲观锁)认为:万一辅导员说这次放假延迟,大家都留校等领导通知,于是他就在宿舍一直等到辅导员通知,才开始收拾行李,出发回家。

此时,在B等待的时间里,A可能已经到家了。(即A的回家效率高于B


2. 轻量级锁——重量级锁

该文中出现的“乐观锁”“偏向锁”“等都会在后面介绍,读者不必先理解,可先大致有个印象

轻量级锁

  • 加锁解锁,过程更快更高效

  • 轻量级锁在Java中是一种乐观锁的方式,使用CAS(比较和交换)实现,它是通过在对象头中标记为“偏向锁”来实现的。当一个线程获得该偏向锁时,它就可以直接访问被锁定的对象,而不用执行任何额外的同步操作。如果有其他线程来访问该对象,轻量级锁就会自动退化为重量级锁。


重量级锁

  • 加锁解锁,过程更慢更低效

  • 重量级锁在Java中是一种悲观锁的方式,使用互斥锁(Mutex Lock)实现,它需要操作系统的支持。当有多个线程同时访问一个共享资源时,重量级锁会把其他线程阻塞,直到当前线程执行完毕,释放锁。这种方式的效率较低,因为线程的上下文切换和系统调用开销较大。

总结

  1. 轻量级锁适用于竞争不激烈的情况,而重量级锁适用于竞争激烈的情况。在实际开发中,我们需要根据具体场景选择合适的锁机制,以达到最佳的性能。

  2. 同时,乐观锁可能是轻量级锁,悲观锁可能是重量级锁(不绝对)


3. 自旋锁——挂起等待锁

自旋锁

  • 一直占用CPU,不涉及线程阻塞和调度,持续不断的请求锁,一但锁被释放,就能立即得到,忙等
  • 如果其他线程一直不释放锁,那它就一直持续消耗CPU资源(该代码通常是纯用户态,不会设置很长的时间)

挂起等待锁

  • 当它发现没有锁的时候,就会进入挂起等待状态(挂机),挂起等待的时候是
    不消耗 CPU的

  • 它等待操作系统的通知唤醒,但是可能其他线程刚释放了锁,就被一直不断请求的自旋锁线程给枪走了,所以它只能继续等待,具体拿到锁的时机,还得听从操作系统的安排(该锁一般是内核机制,可能会等待较长的时间)

对照前文

  1. 自旋锁是轻量级锁的一种典型实现

  2. 挂起等待锁是重量级锁的一种典型实现


4. 互斥锁——读写锁

互斥锁(例如:synchronized)

只有两个操作:

  1. 进入代码块,给该代码块加锁。

  2. 出代码块,解锁该带代码块。

  3. 互斥锁常用于保护共享数据结构的访问,如队列、链表、散列表等。需要注意的是,互斥锁使用不当可能会带来锁竞争、死锁等问题,

读写锁(例如:ReentrantReadWriteLock)

  1. 给读操作加锁。(读锁,是一种共享锁,可被多个线程同时拥有。当读锁被占用时,其他读锁可以继续被占用。共享性。)

  2. 给写操作加锁。(写锁,写锁是一种排他锁,只能被一个线程占用,当写锁被占用时,其他任何锁都不能被占用。原子性。)

  3. 解锁。

  4. 多个线程同时读取一个变量,不会涉及到线程安全问题。读写锁适用于对共享资源的读操作频繁,写操作较少的情况,如高并发读,比如缓存、数据维护等。读写锁可以提高读取效率,避免了互斥锁的性能开销。同时,写操作的排他特性避免了并发写操作对共享资源的影响,保证数据的正确性和一致性。

在读锁和写锁之间,约定:

  • 读锁和读锁之间,不会锁竞争,不会产生阻塞等待。(不会影响执行速度)

  • 写锁和写锁之间,有锁竞争。(不会影响执行速度)

  • 读锁和写锁之间,有锁竞争。(会影响速度,但是保证线程安全)


5. 可重入锁——不可重入锁

可重入锁,又名递归锁(例如:synchronized)

  1. 如果一个锁,在一个线程中,连续加锁两次,不死锁,就叫做可重入锁,死锁了,就叫不可重入锁。即允许同一个线程多次获取同一把锁,而不会产生死锁。

  2. 这种锁能够保证同一线程多次访问同一资源时不会发生冲突。

  3. Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的。

代码示例

Object locker = new Object();
synchronized(locker) {
    synchronized(locker) {
        //连续加锁两次    
    }
}

//或者
//这也是两次加锁,针对this
class BlockingQueue {
    synchronized void put(int elem) {
        this.size();
    }
    
    synchronized int size() {}
}

不可重入锁

  1. 同一线程第二次加锁的时候, 会阻塞等待。直到第一次的锁被释放, 才能获取到第二个锁。 但是释放第一个锁也是由该线程来完成, 结果这个线程已经阻塞了, 也就无法进行解锁操作.。这时候就会死锁。

  2. 即在同一线程再次请求获得该锁时,会造成死锁。因为该锁只能被获得一次,并且只有获得锁的线程才能释放锁。

  3. Linux系统提供的 mutex是不可重入锁.


6. 公平锁——非公平锁

公平锁

  1. 是指多个线程按照申请锁的顺序来获取锁,即先到先得的策略。(公不公平是由自己对公平的定义决定,Java中定义先到先得为公平,synchronized为非公平锁,它遵循等概率竞争规则)

  2. 公平锁的优点是可以避免饥饿现象,即线程在获取锁时会受到先来先服务的原则,公平性是保证锁最大程度分配给等待时间最长的线程,缺点是其效率较低,因为需要保存大量的线程状态。

非公平锁

  1. 多个线程获取锁的顺序是不确定的,有可能后申请锁的线程先获取到锁,这种方式可能造成某些线程一直无法获取到锁。

  2. 在Java中,ReentrantLock默认就是非公平锁。与公平锁相比,非公平锁调度的效率要高,但是不公平的分配策略可能会导致某些线程一直无法获取到锁,从而产生“饥饿”的现象。

  3. 在Java中,ReentrantLock默认是非公平锁,可以通过它的构造函数改为公平锁。


二、Synchronized——ReentrantLock

Synchronized的特点(JDK1.8)

  1. 开始时是乐观锁,如果锁冲突频繁,就转换为悲观锁。

  2. 开始时轻量级锁,如果锁被持有时间较长,就转换为重量级锁。

  3. 轻量级锁大概率基于自旋实现,重量级锁大概率基于挂起等待实现。

  4. 不是读写锁。

  5. 是可重入锁。

  6. 是非公平锁。

Synchronized的锁升级策略

都是尽可能减少锁带来的的开销

在这里插入图片描述

  • 无锁

  • 偏向锁(非必要不加锁

即线程对锁有个标记,没有竞争就不加锁,倘若有别的线程竞争,就立即加锁,即高效又安全

  • 自旋锁 / 轻量级锁(遇到了锁竞争,但是目前线程较少,就让它自旋一会,说不定很快就拿到了 )

  • 重量级锁(线程竞争激烈,多个线程都在自旋,大量占用cpu资源,直接升级锁,调用系统内核阻塞等待)

主流的JVM只能锁升级,不能降级,不是实现不了,可能需要付出更大的代价,于是干脆就不降级了

ReentrantLock的特点

  1. 可重入:同一个线程可以多次获取锁,避免了死锁的发生。

  2. 公平锁和非公平锁:ReentrantLock可以通过参数指定是公平锁还是非公平锁。

  3. 条件变量:ReentrantLock可以通过维护条件变量来实现线程间的协调。

  4. 中断响应:ReentrantLock支持线程中断,即在等待锁的过程中,可以响应中断信号。

  5. 限时等待:ReentrantLock支持线程等待一定时间,如果在指定时间内还未获取到锁,就会放弃等待。

Synchronized和ReentranLock对比

  1. ReentranLock是可重入锁,提供lock()unlock()独立方法(即需要手动释放),来进行加锁解锁,synchronized也是可重入锁(基于代码块的方式来控制加锁解锁),它在第二次加锁之前,会判定当前锁的拥有者是否是同一个线程,如果是,则直接放行,不必再加一次锁

  2. synchronized是非公平的,若想要公平,需要手动加个优先级队列来记录顺序。ReentrantLock提供公平和非公平两种工作模式,默认是非公平锁, 在构造方法中传入true,开启公平锁。

  3. synchronized搭配Objectwaitnotify进行等待唤醒,如果多个线程wait()同一个对象,notify()随机唤醒一个。ReentrantLock需要搭配Condition这个类,这个类也能起到等待通知的作用,能够精准唤醒某个线程, 功能更强大。

  4. synchronized是一个关键字, 是 JVM内部实现的(大概率是基于 C++ 实现). ReentrantLock是标准库的一个类, 在 JVM 外实现的(基于Java实现)

  5. synchronized在申请锁失败时, 会死等. ReentrantLock可以通过 trylock()的方式等待一段时间就放弃, 不会阻塞,而是返回false(让用户自己决定后续操作)。

三、锁消除——锁粗化

锁消除

  • 非必要不加锁(不滥用synchronized)

  • 编译器+JVM就会会作出优化,检测当前代码是否是多线程执行 / 是否有必要加锁,如果没必要,就自动把锁去掉。

例如:StringBuilder和StringBuffer,后者带锁,但是如果单线程使用后者,就自动将后者优化为前者。(该手段十分保守,只有保证消除是可靠的,才会启动,宁愿什么也不做,也不愿意犯错

锁粗化

在这里插入图片描述

  • 锁的粒度,synchronized代码块,包含代码的多少(代码越多,粒度越粗。代码越少,粒度越细)

  • 一般写代码,多数情况下,希望粒度小一些。(串行执行的代码少,并发执行的代码多)但是如果某个场景,频繁的加锁/解锁,此时编译器就会把它优化为一个更粗粒度的锁。

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

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

相关文章

掌握Python的X篇_13_Python条件语句实例:判断闰年、成绩评定

前面学习了条件语句以及调试的基本技巧,本篇介绍两个与条件语句有关的实例,对前面的知识又深刻认识。 文章目录 1. 判断闰年1.1 版本11.2 版本21.3 一行代码太长的处理方法 2. 根据成绩评级 1. 判断闰年 用户输入年份,判断该年份是否为闰年…

相对位置编码和绝对位置编码

位置编码的区别: 相对位置编码和绝对位置编码是两种不同的位置编码方法。 绝对位置编码是一种基于位置嵌入的方法,其中每个位置都被分配了一个唯一的位置向量。这些向量是固定的,与输入序列的内容无关。这种编码方式对于处理较短的序列效果…

【图论】树上差分(点差分)

一.题目 输入样例: 5 10 3 4 1 5 4 2 5 4 5 4 5 4 3 5 4 3 4 3 1 3 3 5 5 4 1 5 3 4 输出样例:9 二 .分析 我们可以先建一棵树 但我们发现,这样会超时。 所以,我们想到树上差分 三.代码 /* 5 10 3 4 1 5 4 2 5 4 5 4 5 4 3 5 …

基金经理二季度AI概念股操作分化

公募基金二季度仍在加仓AI板块,但不同于一季度全线加仓题材各环节,二季度对AI产业链的操作出现分化。 资金更加聚拢在业绩率先兑现的上游算力板块。其中光模块、服务器是加仓最为显著的两个领域;对于部分业绩短期兑现前景不明的AI板块&#…

机器学习 day31(baseline、学习曲线)

语音识别的Jtrain、Jcv和人工误差 对于逻辑回归问题,Jtrain和Jcv可以用分类错误的比例,这一方式来代替单单只看Jtrain,不好区分是否高偏差。可以再计算人类识别误差,即人工误差,作为基准线来进行比较Jtrain与baselin…

论文分享:PowerTCP: Pushing the Performance Limits of Datacenter Networks

1 原论文的题目(中英文)、题目中包含了哪些关键词?这些关键词的相关知识分别是什么? 题目:PowerTCP: Pushing the Performance Limits of Datacenter Networks PowerTCP:逼近数据中心的网络性能极限 2 论…

银河麒麟安装mysql数据库(mariadb)-银河麒麟安装JDK-银河麒麟安装nginx(附安装包)

银河麒麟离线全套安装教程(手把手教程) 1.银河麒麟服务器系统安装mysql数据库(mariadb) 2.银河麒麟桌面系统安装mysql数据库(mariadb) 3.银河麒麟服务器系统安装JDK 4.银河麒麟桌面系统安装JDK 5.银河麒麟…

【Linux后端服务器开发】MAC地址与其他重要协议

目录 一、以太网 二、MAC地址 三、MTU 四、ARP协议 五、DNS系统 六、ICMP协议 七、NAT技术 八、代理服务器 一、以太网 “以太网”不是一种具体的网路,而是一种技术标准:既包含了数据链路层的内容,也包含了一些物理层的内容&#xf…

Linuxcnc-ethercat从入门到放弃(1)、环境搭建

项目开源网站 LinuxCNChttps://www.linuxcnc.org/当前release版本2.8.4 Downloads (linuxcnc.org)https://www.linuxcnc.org/downloads/可以直接下载安装好linuxcnc的实时debian系统,直接刻盘安装就可以了 安装IgH主站,网上有很多教程可供参考 git clo…

【Rust】枚举类型创建单链表以及常见的链表操作方法

目录 单链表 用枚举表达链表 枚举enum Box容器 创建节点 1. 创建并打印 2. match 匹配 3. 节点初始化 4.节点嵌套 追加节点 1. 尾插法 2. 链表追加方法 3. 头插法 4. 改写成单链表方法 遍历链表 1. 递归法 2. 递推法 3. 改写成单链表方法 自定义Display tr…

8. Vmvare中重新分配Linux系统的分区空间大小

1. 说明 一般情况下,在使用Vmvare虚拟机创建配置Linux系统时,默认将系统的内存设置为4GB,硬盘大小设置为40GB,但随着空间利用的越来越多,内存会出现不够使用的情况,此时需要重新分配空间大小,具…

go 查询采购单设备事项[小示例]

一、项目背景 1.1需求: 项目实施过程中存在多次下采购单的事项,如果查询过去采购单中下了哪些设备,数量以及相应信息,如何处理呢? 备注,价格等都是修改了,不是原始内容,只是参考 1.2实现步骤…

transformer代码注解

其中代码均来自李沐老师的动手学pytorch中。 class PositionWiseFFN(nn.Module):ffn_num_inputs 4ffn_num_hiddens 4ffn_num_outputs 8def __init__(self,ffn_num_inputs,ffn_num_hiddens,ffn_num_outputs):super(PositionWiseFFN,self).__init__()self.dense1 nn.Linear(ffn…

3ds MAX绘制简单动画

建立一个长方体和茶壶: 在界面右下角点击时间配置: 这是动画制作的必要步骤 选择【自动】,接下来,我们只要在对应的帧改变窗口中图形的位置,就能自动记录该时刻的模样 这就意味着,我们通过电脑记录某几个…

工业平板电脑优化汽车工厂的生产流程

汽车行业一直是自动化机器人系统的早期应用领域之一。通过使用具有高负载能力和远程作用的大型机械臂,汽车装配工厂可以实现点焊、安装挡风玻璃、安装车轮等工作,而较小的机械手则用于焊接和安装子组件。使用机器人系统不仅提高了生产效率,还…

工业智能化的关键之二:集成监控和分析能力

将监控和分析能力集成到工厂运营的日常中是工业智能化发展的关键步骤。随着科技的进步和数字化技术的广泛应用,工厂正在逐步实现从传统的人工操作到智能化的转变。这种转变不仅提高了工厂的生产效率和产品质量,还极大地提升了工厂的安全性和可靠性。 1.…

Flutter 调试工具篇 | 壹 - 使用 Flutter Inspector 分析界面

theme: cyanosis 1. 前言 很多朋友可能在布局过程中、或者组件使用过程中,会遇到诸如颜色、尺寸、约束、定位等问题,可能会让你抓耳挠腮。俗话说,磨刀不误砍柴工,会使用工具是非常重要的,其实 Flutter 提供了强大的调试…

axios使用异步方式无感刷新token,简单,太简单了

文章目录 🍉 废话在前🍗 接着踩坑🥩 解决思路🍓 完整代码 🍉 废话在前 写vue的或帮们无感刷新token相信大家都不陌生了吧,刚好,最近自己的一个项目中就需要用到这个需求,因为之前没…

Fluentbit

Fluent Bit(常简称为Fluent-Bit 或 Fluentbit)是一个开源的、轻量级的日志数据收集器(log collector)和 转发器(log forwarder),旨在高效地收集、处理和转发日志数据。它是Fluentd项目的一个子项…

山东农业大学图书馆藏书《乡村振兴战略下传统村落文化旅游设计》

山东农业大学图书馆藏书《乡村振兴战略下传统村落文化旅游设计》