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

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 一、六大"有锁策略"
    • 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代码块,包含代码的多少(代码越多,粒度越粗。代码越少,粒度越细)

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

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

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

相关文章

【C语言数据结构】模拟·顺序表·总项目实现

💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤 📃个人主页 :阿然成长日记 …

leetcode 376. 摆动序列

2023.7.28 本题思路是定义一个 direct变量记录上一次摆动是上坡还是下坡 。 然后在一个for循环中循环判断当前摆动和上一次摆动是否一致,如果不一致则视为一次摆动。 如果前后元素值相等得话,直接continue进入下一次循环。 下面看代码: clas…

leaftjs实现全国温度降水气压风速等值面风场洋流效果

实现内容 数据爬取、地图marker聚合、鼠标移动显示pop,风场,洋流,温度等值面、降水等值面、气压等值面、风速等值面,洋流方向、洋流流速展示、风场方向、风场风速展示,后期扩展小时预报,分钟预报、7天预报…

Matplotlib_概述_绘制图象

⛳绘制基础 在使用 Matplotlib 绘制图形时,其中有两个最为常用的场景。一个是画点,一个是画线。 pyplot 基本方法的使用如下表所示 方法名说明title()设置图表的名称xlabel()设置 x 轴名称ylabel()设置 y 轴名称xticks(x, ticks, rotation)设置 x 轴的…

(el-radio)操作:Element-plus 中 Radio 单选框改成垂直排列的样式操作与使用

Ⅰ、Element-plus 提供的Radio单选框组件与想要目标情况的对比: 1、Element-plus 提供 Radio 组件情况: 其一、Element-ui 自提供的Radio代码情况为(示例的代码): // Element-plus 自提供的代码: // 此时是使用了 ts 语言环境&a…

4.操作元素属性

4.1操作元素常用属性 ●通过 JS 设置/修改 标签元素属性,比如通过src更换图片 ●最常见的属性比如:href、 title、 src 等 ●语法: 对象.属性 值【示例】 // 1.获取元素 const pic document.querySelector( img ) // 2.操作元素 pic.src ./images/b…

商品库存管理系统设计与实现(Vue+SpringBoot+MySQL)

一、项目背景 当今,我国科技发展日新月异,各类企业迅速崛起,商品类型日益繁多,产品数量急剧增加,企业经营模式越来越多样,信息处理量不断加大,对库存管理提出了更高的要求。通过本系统&#xff…

LayUi 树形组件tree 实现懒加载模式,展开父节点时异步加载子节点数据

如题。 效果图&#xff1a; //lazy属性为true&#xff0c;点开时才加载 引用代码&#xff1a; <link href"~/Content/layui-new/css/layui.css" rel"stylesheet" /><form id"form" class"layui-form" style"margin-to…

数据库索引优化与查询优化——醍醐灌顶

索引优化与查询优化 哪些维度可以进行数据库调优 索引失效、没有充分利用到索引-一索引建立关联查询太多JOIN (设计缺陷或不得已的需求) --SQL优化服务器调优及各个参数设置 (缓冲、线程数等)–调整my.cnf数据过多–分库分表 关于数据库调优的知识点非常分散。不同的 DBMS&a…

YOLOv5:使用7.0版本训练自己的实例分割模型(车辆、行人、路标、车道线等实例分割)

YOLOv5&#xff1a;使用7.0版本训练自己的实例分割模型&#xff08;车辆、行人、路标、车道线等实例分割&#xff09; 前言前提条件相关介绍使用YOLOv5-7.0版本训练自己的实例分割模型YOLOv5项目官方源地址下载yolov5-7.0版源码解压目录结构 准备实例分割数据集在./data目录下&…

Rust vs Go:常用语法对比(七)

题图来自 Go vs Rust: Which will be the top pick in programming?[1] 121. UDP listen and read Listen UDP traffic on port p and read 1024 bytes into buffer b. 听端口p上的UDP流量&#xff0c;并将1024字节读入缓冲区b。 import ( "fmt" "net&qu…

vue+leaflet笔记之地图聚合

vueleaflet笔记之地图聚合 文章目录 vueleaflet笔记之地图聚合开发环境代码简介插件简介与安装使用简介 详细源码(Vue3) 本文介绍了Web端使用Leaflet开发库进行地图聚合查询的一种方法 (底图来源:中科星图)&#xff0c;结合Leaflet.markercluster插件能够快速的实现地图聚合查询…

数据库数据恢复-Syabse数据库存储页底层数据杂乱的数据恢复案例

数据库恢复环境&#xff1a; Sybase版本&#xff1a;SQL Anywhere 8.0。 数据库故障&#xff1a; 数据库所在的设备意外断电后&#xff0c;数据库无法启动。 错误提示&#xff1a; 使用Sybase Central连接后报错&#xff1a; 数据库故障分析&#xff1a; 经过北亚企安数据恢复…

内存函数讲解

&#x1f495;"痛苦难以避免&#xff0c;而磨难可以选择。"-->村上春树&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;数据在内存中的存储 内存函数就是管理内存数据的函数&#xff0c;包含于头文件<string.h>中 1.memcpy函数-->内存…

机器学习——异常检测

异常点检测(Outlier detection)&#xff0c;⼜称为离群点检测&#xff0c;是找出与预期对象的⾏为差异较⼤的对象的⼀个检测过程。这些被检测出的对象被称为异常点或者离群点。异常点&#xff08;outlier&#xff09;是⼀个数据对象&#xff0c;它明显不同于其他的数据对象。异…

soft ip与hard ip

ip分soft和hard两种&#xff0c;soft就是纯代码&#xff0c;买过来要自己综合自己pr。hard ip如mem和analog与工艺有关。 mem的lib和lef是memory compiler产生的&#xff0c;基于bitcell&#xff0c;是foundry给的。 我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起…

pyspark 笔记 cast 转换列的类型

1 不借助 pyspark.sql.types from pyspark.sql.functions import coldata [("Alice", "28"), ("Bob", "22"), ("Charlie", "30")] columns ["name", "age_str"] df spark.createDataFram…

第一章 计算机网络概述

第一章 计算机网络概述 1.1 计算机网络在信息时代的作用 1.2 因特网概述 网络分类&#xff1a; 网络&#xff1a;许多计算机连接在一起的的局域网&#xff1b; 互联网&#xff1a;internet许多网络连接在一起&#xff1b; 因特网&#xff1a;Internet 全球最大的互联网&…

15.Netty源码之EventLoop

highlight: arduino-light Netty配置主从Reactor模式 通过将NioServerSocketChannel绑定到了bossGroup。 将NioServerSocketChannel接收到请求创建的SocketChannel放入workerGroup。 将2个不同的SocketChannel绑定到2个不同的Group完成了主从 Reactor 模式。 分配NIOEventLoop的…

【java安全】RMI

文章目录 【java安全】RMI前言RMI的组成RMI实现Server0x01 编写一个远程接口0x02 实现该远程接口0x03 Registry注册远程对象 Client 小疑问RMI攻击 【java安全】RMI 前言 RMI全称为&#xff1a;Remote Method Invocation 远程方法调用&#xff0c;是java独立的一种机制。 RM…