多线程编程中的锁策略

目录

1.悲观锁vs乐观锁

关键总结

悲观锁:

乐观锁:

选择建议

用 悲观锁 当:

用 乐观锁 当:

2.重量级锁vs轻量级锁 

选择建议

用 轻量级锁:

用 重量级锁:

3.挂起等待锁vs自旋锁

 关键细节说明

选择建议

4.普通互斥锁vs读写锁

       读写锁互斥规则详解

       普通互斥锁 vs 读写锁对比

5.可重入锁vs不可重入锁 

选择建议

6.公平锁vs不公平锁

1) 公平锁和不公平锁的对比

2)选择建议

7.锁升级

8.锁优化

9.锁的粗化

10.synchronized

11.reentrantlock和synchronized的对比


锁策略(Locking Strategies)是多线程编程中用于控制并发访问共享资源的核心机制。不同的锁策略在性能、公平性、复杂度等方面有显著差异。


以下来介绍几组常见的锁策略:

1.悲观锁vs乐观锁

悲观锁:在加锁的时候预测接下来的竞争会非常激烈,所以就需要针对这种激烈的竞争情况做一些准备工作:每次拿数据的时候都会上锁,这样别人想要拿数据的时候就会阻塞知道它拿到锁。

乐观锁:在加锁的时候预测接下来的竞争不会非常激烈,也就一般不会发生并发冲突,不必多做准备。在数据进行提交更新的时候再检测是否产生并发冲突。

特性悲观锁乐观锁
核心思想假设并发冲突一定会发生,因此先加锁再访问数据。假设并发冲突较少发生,因此不加锁,通过版本号/CAS机制检测冲突。
实现方式直接加锁(如 synchronizedReentrantLock、数据库 SELECT FOR UPDATE)。无锁机制(如 CAS、版本号校验、AtomicXXX 类、数据库 乐观并发控制)。
阻塞行为❗ 会阻塞其他线程(未获取锁的线程必须等待)。✅ 无阻塞,线程直接操作数据,冲突时通过重试或回滚解决。
适用场景写多读少(如银行转账、库存扣减等强一致性场景)。读多写少(如缓存、统计计数等低冲突场景)。
性能开销高(锁竞争、上下文切换、死锁风险)。低(无锁竞争,但冲突频繁时重试开销增大)。
数据一致性强一致性(锁内操作串行化)。最终一致性(冲突时需业务层处理)。
典型实现Java: synchronizedReentrantLock
数据库: SELECT FOR UPDATE
Java: AtomicIntegerStampedLock
数据库: 版本号字段CAS操作
优缺点✅ 保证强一致性
❌ 吞吐量低,可能死锁。
✅ 高并发性能
❌ 需处理冲突,可能活锁或重试饥饿。

关键总结

  1. 悲观锁

    • “先保护再操作”,适合强一致性场景,但性能较差。

    • 例如:synchronized 关键字、数据库行锁。

  2. 乐观锁

    • “先操作再校验”,适合高并发读场景,冲突少时性能极高。

    • 例如:CAS 操作、StampedLock 的乐观读模式。


选择建议

  • 用 悲观锁 当:

    • 写操作频繁,或冲突概率高(如支付系统)。

    • 业务逻辑复杂,需严格保证数据一致性。

  • 用 乐观锁 当:

    • 读多写少,且冲突概率低(如商品浏览统计)。

    • 追求高吞吐量,能容忍重试或短暂不一致。

2.重量级锁vs轻量级锁 

重量级锁:应对悲观的场景,此时就要付出更多的代价,更低效。

轻量级锁:应对乐观的场景,此时付出的代价更小,更高效。

特性重量级锁轻量级锁
锁级别最高级别的锁(如操作系统级互斥锁)JVM 层面的优化锁(基于 CAS 自旋)
实现原理通过操作系统内核的 互斥量(Mutex) 实现,涉及用户态/内核态切换。通过 CAS(Compare-And-Swap) 和 线程栈中的锁记录(Lock Record) 实现。
阻塞方式未获取锁的线程会 直接挂起(进入内核态等待队列),由操作系统调度唤醒。先通过 自旋(忙等待) 尝试获取锁,失败后才升级为重量级锁。
性能开销高(上下文切换、内核态切换、CPU 调度延迟)。低(无系统调用,仅在用户态自旋)。
适用场景高竞争、长临界区(如长时间持有锁的复杂操作)。低竞争、短临界区(如快速计算的简单操作)。
锁升级是锁膨胀的最终状态(轻量级锁/偏向锁失败后升级)。是偏向锁失败后的中间状态,可能进一步升级为重量级锁。
典型例子synchronized 在竞争激烈时的最终状态。synchronized 在低竞争时的优化状态。
优点严格保证线程安全,适合高并发冲突场景。减少内核态开销,提高短任务性能。
缺点吞吐量低,响应延迟高。自旋浪费 CPU(长时间自旋可能反而降低性能)。

选择建议

  • 用 轻量级锁

    • 代码块执行时间极短(如计数器递增)。

    • 线程竞争概率低(如局部变量同步)。

  • 用 重量级锁

    • 临界区执行时间长(如数据库事务)。

    • 竞争激烈(如全局缓存更新)。

3.挂起等待锁vs自旋锁

挂起等待锁:(重量级锁的经典实现)一旦发生竞争,获取不到锁,就进入阻塞状态。

自旋锁:(轻量级锁的经典实现)一旦发生竞争,获取不到锁,就循环请求,也就是“忙等”。发生在“乐观”情况下,锁竞争很小,即使“忙等”,很快也能获取锁,等待的时间也不会很长 ,消耗的资源也就不会很多。

特性挂起等待锁(阻塞锁)自旋锁
实现原理获取不到锁时,线程进入阻塞状态(WAITING),释放CPU资源获取不到锁时,线程循环忙等待(自旋),不释放CPU
响应速度较慢(需要线程切换和唤醒)极快(无上下文切换)
CPU占用低(线程挂起后不消耗CPU)高(持续占用CPU自旋)
适用场景锁持有时间较长(如I/O操作、复杂计算)锁持有时间极短(如CAS操作、简单计算)
公平性通常支持公平锁(按申请顺序获取锁)通常是非公平锁(竞争机制)
锁升级可能涉及内核态切换(如synchronized重量级锁)完全在用户态运行(无系统调用)
典型实现Java的synchronized(竞争激烈时)、ReentrantLockJava的AtomicIntegerSpinLock(自定义实现)
优点节省CPU资源,适合高竞争场景无上下文切换,延迟极低
缺点线程切换开销大,响应延迟高自旋过久会浪费CPU,可能饥饿
死锁风险可能死锁(需超时或中断机制)无死锁(但可能活锁)

 关键细节说明

  1. 挂起等待锁(阻塞锁)

    • 适用场景:锁竞争激烈或临界区执行时间较长(如数据库事务)。

    • 优化建议

      • 使用ReentrantLock替代synchronized(支持超时和中断)。

      • 设置合理的锁超时时间(避免死锁)。

  2. 自旋锁

    • 适用场景:锁持有时间极短(如计数器递增)。

    • 优化建议

      • 限制自旋次数(如JVM的-XX:PreBlockSpin参数)。

      • 升级为自适应自旋锁(根据历史自旋时间动态调整)。


选择建议

  • 优先用自旋锁

    • 临界区代码执行时间 < CPU上下文切换时间(通常约1μs)。

    • 例如:AtomicInteger的CAS操作。

  • 优先用挂起等待锁

    • 临界区代码执行时间 > 自旋消耗的CPU时间

    • 例如:同步访问数据库连接池。

4.普通互斥锁vs读写锁

普通互斥锁:锁之间完全互斥,同一时间只能有一个线程持有锁,分为‘加锁’和‘解锁’两个过程。synchronized就是经典的普通互斥锁。

读写锁:应对某些读多写少的情况,分为“读锁”和“写锁”两种,其中“读锁”和“读锁”之间不互斥,支持了多个线程同时读取的情况,减少了锁冲突的情况。

       读写锁互斥规则详解

  1. 读锁 vs 读锁

    • 不互斥:多个线程可同时持有读锁(共享)。

    • 示例:100个线程可同时读取缓存数据。

  2. 读锁 vs 写锁

    • 完全互斥

      • 已有读锁时,写锁请求被阻塞(直到所有读锁释放)。

      • 已有写锁时,读锁请求被阻塞(直到写锁释放)。

  3. 写锁 vs 写锁

    • 完全互斥:同一时刻只允许一个写锁存在。

       普通互斥锁 vs 读写锁对比

特性普通互斥锁(Mutex Lock)读写锁(Read-Write Lock)
锁模式排他锁(Exclusive Lock)读锁(共享锁) + 写锁(排他锁)
并发性完全互斥,同一时刻只有一个线程能持有锁读锁可共享(允许多线程同时读),写锁互斥
适用场景写操作频繁或读写操作时间相近读多写少(如缓存、配置读取)
性能高竞争下性能差读并发高,写操作会阻塞所有读/写
公平性支持公平/非公平(如ReentrantLock通常支持公平/非公平(如ReentrantReadWriteLock
锁降级不支持支持(持有写锁的线程可以获取读锁,再释放写锁)
锁升级不支持不支持(读锁不能直接升级为写锁,易死锁)
实现复杂度简单较复杂(需维护读/写状态)
典型实现Java: synchronizedReentrantLockJava: ReentrantReadWriteLock
优点实现简单,严格保证一致性读操作高并发,适合读多写少场景
缺点读写均互斥,并发性差写操作饥饿风险(长期有读锁时写锁无法获取)

5.可重入锁vs不可重入锁 

可重入锁:允许同一个线程多次获取一把锁。

不可重入锁:不允许同一个线程多次获取同一把锁。

特性可重入锁不可重入锁
递归调用支持✅ 同一线程可重复获取同一把锁(计数器递增)❌ 同一线程重复获取会死锁
死锁风险低(允许嵌套加锁)高(递归调用或嵌套加锁直接死锁)
实现复杂度较高(需维护持有线程和重入次数)简单(仅检查锁是否被占用)
典型应用场景递归函数、同步方法调用其他同步方法简单临界区保护(无嵌套调用场景)
性能开销略高(需维护重入状态)较低(无状态跟踪)
锁释放要求必须释放与获取次数匹配(如加锁3次需解锁3次)只需释放1次
公平性支持通常支持(如ReentrantLock(true)通常不支持
中断响应支持(lockInterruptibly()一般不支持
实现示例Java: synchronizedReentrantLock
C++: std::recursive_mutex
Java: 自定义简单锁
C: pthread_mutex(默认不可重入)
适用性通用场景(尤其是复杂同步逻辑)极简场景(无嵌套/递归)

选择建议

  • 用可重入锁

    • 存在方法嵌套/递归调用同步代码。

    • 使用类库提供的锁(如Java的synchronized)。

  • 用不可重入锁

    • 绝对无嵌套的简单临界区(性能敏感场景)。

    • 需要极轻量级同步原语(如自定义自旋锁)。

6.公平锁vs不公平锁

公平锁:线程采用“先来后到”的思想来依次获取锁。

不公平锁:线程采用“概率相等”的思想来获取锁。

1) 公平锁和不公平锁的对比

特性公平锁 (Fair Lock)非公平锁 (Non-Fair Lock)
获取锁顺序严格按照线程请求顺序(先到先得)允许插队(新线程可能直接抢到锁)
吞吐量较低(线程切换频繁)较高(减少线程切换)
线程饥饿不会发生(保证每个线程有机会执行)可能发生(某些线程长期抢不到锁)
实现复杂度较高(需维护等待队列)较低(直接竞争)
适用场景要求严格公平性(如订单处理、金融交易)高并发场景,追求性能(如缓存、计数器)
锁获取策略检查是否有等待更久的线程,有则排队直接尝试获取锁,失败才进入队列
典型实现ReentrantLock(true)ReentrantLock(false)(默认)、synchronized
响应时间较稳定(按序执行)波动较大(可能某些线程更快)
CPU利用率较低(线程频繁挂起/唤醒)较高(减少上下文切换)
锁竞争激烈时所有线程按序执行,但吞吐量下降可能某些线程一直抢到锁,其他线程等待更久

2)选择建议

场景推荐锁类型原因
需要严格顺序(如交易系统)公平锁避免某些请求被无限延迟
高并发、低延迟(如缓存)非公平锁减少线程切换,提高吞吐量
线程执行时间差异大公平锁防止短任务“饿死”长任务
锁竞争不激烈非公平锁插队概率低,性能接近公平锁但开销更小

7.锁升级

锁升级是多线程并发控制中的一种优化策略,指JVM根据竞争情况动态调整锁的级别,从低开销锁逐步升级为高开销锁的过程。

无锁 → 偏向锁 → 轻量级锁 → 重量级锁
锁级别升级时机特点适用场景
无锁对象未被任何线程锁定新建对象
偏向锁

代码进入synchronized代码块

=>偏向锁

仅记录线程ID,无实际同步,只是一个标记,不是锁单线程访问
轻量级锁

其他线程尝试获取这个锁

=>轻量级锁

CAS自旋尝试获取锁低竞争多线程
重量级锁

JVM发现,当前竞争锁的情况非常激烈

=>重量级锁

操作系统互斥量实现高竞争场景

总的来说,锁升级是懒汉模式思想的一种体现,旨在减少开销。锁升级只升不降,不可以再转化回去。

8.锁优化

锁优化是提升多线程程序性能的关键手段,JIT编译器通过逃逸分析移除不必要的锁。旨在减少锁的开销。

9.锁的粗化

锁粗化是JVM针对同步代码的一种重要优化技术,它通过合并相邻的同步块减少不必要的锁获取/释放操作,从而提升程序执行效率。

粒度级别含义
粗粒度在一把锁之间代码更多
细粒度在一把锁之间代码更少

 锁的粗化实际上就是通过将多把锁合并为一把锁来减少频繁上锁、解锁的开销,使得一把锁之间的代码变得更多。

10.synchronized

锁的类型synchronized
悲观锁还是乐观锁?自适应
重量级锁还是轻量级锁?自适应
挂起等待锁还是自旋锁?自适应
普通互斥锁还是读写锁?普通互斥锁

可重入锁还是不可重入锁?

可重入锁
公平锁还是不公平锁?公平锁

11.reentrantlock和synchronized的对比

ReentrantLock 是 Java 并发包(java.util.concurrent.locks)中提供的可重入互斥锁实现,它比传统的 synchronized 关键字提供更灵活的锁操作。 

以下是reentrantlocksynchronized的五点区别:

区别reentrantlocksynchronized
实现方式Java标准库中的类,由JDK实现关键字,由JVM实现,JVM基于C++实现
加锁方法通过lock()和unlock()方法加锁和解锁,有可能因为忘记解锁而触发线程安全问题通过代码块控制加锁、解锁
trylock()方法具备trylock()方法,尝试获取锁而不会无限期阻塞不具备
公平锁与非公平锁默认是非公平锁,但是也可以实现公平锁非公平锁
等待通知机制等待通知机制是condition类,比synchronized的wait、notify功能更为强大wait、notify

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

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

相关文章

负载均衡是什么,Kubernetes如何自动实现负载均衡

负载均衡是什么&#xff1f; 负载均衡&#xff08;Load Balancing&#xff09; 是一种网络技术&#xff0c;用于将网络流量&#xff08;如 HTTP 请求、TCP 连接等&#xff09;分发到多个服务器或服务实例上&#xff0c;以避免单个服务器过载&#xff0c;提高系统的可用性、可扩…

React-01React创建第一个项目(npm install -g create-react-app)

1. React特点 JSX是javaScript语法的扩展&#xff0c;React开发不一定使用JSX。单向响应的数据流&#xff0c;React实现单向数据流&#xff0c;减少重复代码&#xff0c;比传统数据绑定更简单。等等 JSX是js的语法扩展&#xff0c;允许在js中编写类似HTML的代码 const …

小程序中的网络请求

在小程序中&#xff0c;使用 wx.request( ) 这个方法来发送网路请求&#xff0c;整个请求的方式和 jQuery 里面的 $.ajax 方法是非常相似的。 在 wx.request( ) 这个方法中&#xff0c;接收一个配置对象&#xff0c;该配置对象中能够配置的项目如下表&#xff1a; 关于服务器…

jvm 的attach 和agent机制

Java 的 Attach 和 Agent 机制在实际应用中得到了广泛的成功应用&#xff0c;尤其是在监控、调试、性能分析、故障排查等方面。以下是这两种机制在实际场景中的一些成功应用案例&#xff1a; 1. 性能监控与分析 Java Agent 和 Attach 机制广泛应用于性能监控和分析&#xff0…

基于SpringBoot的“留守儿童网站”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“留守儿童网站”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 局部E-R图 系统首页界面 系统注册…

iPhone XR:一代神机,止步于此

什么样的 iPhone &#xff0c;才配称为一代神机&#xff1f; 我曾经用过iPhone 4S、iPhone 6S Plus、iPhone 8 Plus&#xff0c;iPhone SE2、iPhone XR、iPhone 13、iPhone 14 Plus、iPhone 15/Pro。 不管硬件再怎么卷&#xff0c;不管囊中是否羞涩&#xff0c;主力机基本没考…

【VUE】RuoYi-Vue3项目结构的分析

【VUE】RuoYi-Vue3项目结构的分析 1. 项目地址2. RuoYi-Vue3项目结构2.1 整体结构2.2 package.json2.2.1 &#x1f9fe; 基本信息2.2.2 &#x1f527; 脚本命令&#xff08;scripts&#xff09;2.2.3 &#x1f30d; 仓库信息2.2.4 &#x1f4e6; 项目依赖&#xff08;dependenc…

架构师面试(二十五):分布式存储 Leader 设计

问题 在非常多的分布式存储系统中&#xff0c;如&#xff1a;Zookeeper、Etcd、Kafka等&#xff0c;往往会存在一个 【Leader】 角色&#xff0c;并由该角色负责数据的写入&#xff0c;这样设计最主要的原因是什么呢&#xff1f; A. 唯一负责数据写入的 Leader 角色可以避免并…

使用YoloV5和Mediapipe实现——上课玩手机检测(附完整源码)

目录 效果展示 应用场景举例 1. 课堂或考试监控&#xff08;看到这个学生党还会爱我吗&#xff09; 2. 驾驶安全监控&#xff08;防止开车玩手机&#xff09; 3. 企业办公管理&#xff08;防止工作时间玩手机&#xff09; 4. 监狱、戒毒所、特殊场所安保 5. 家长监管&am…

GPT-4o从语义分割到深度图生成,大模型狂潮下的计算机视觉:技术进步≠替代危机

随着上周&#xff0c;GPT-4o原生多模态图像生成功能的推出&#xff0c;更多玩法也被开发出来。一夜之间&#xff0c;GPT-4o原生多模态能力的释放&#xff0c;让图像生成、语义分割、深度图构建这些曾需要专业工具链支持的复杂任务&#xff0c;变成了普通人输入一句话就能实现的…

Pytorch 张量操作

在深度学习中&#xff0c;数据的表示和处理是至关重要的。PyTorch 作为一个强大的深度学习框架&#xff0c;其核心数据结构是张量&#xff08;Tensor&#xff09;。张量是一个多维数组&#xff0c;类似于 NumPy 的数组&#xff0c;但具有更强大的功能&#xff0c;尤其是在 GPU …

小程序中跨页面组件共享数据的实现方法与对比

小程序中跨页面/组件共享数据的实现方法与对比 在小程序开发中&#xff0c;实现不同页面或组件之间的数据共享是常见需求。以下是几种主要实现方式的详细总结与对比分析&#xff1a; 一、常用数据共享方法 全局变量&#xff08;getApp()&#xff09;、本地缓存&#xff08;w…

vue中的 拖拽

拖拽总结 实现方式特点适用场景HTML5 原生拖拽 API✅ 直接使用 dataTransfer 进行数据传输 ✅ 兼容性好&#xff08;大部分浏览器支持&#xff09; ✅ 适合简单的拖拽场景低代码平台、表单生成器、组件拖拽Vue/React 组件库&#xff08;如 Vue Draggable、SortableJS&#xff…

MySQL 函数(入门版)

目录 一、字符串函数 1、常用的字符串函数 2、函数演示 3、具体案例 二、数值函数 1、常用的数值函数 2、函数演示 3、具体案例 三、日期函数 1、常用的日期函数 2、函数演示 3、具体案例 四、流程函数 1、常用的流程函数 2、函数演示 3、具体案例 在MySQL中&a…

基于快速开发平台与智能手表的区域心电监测与AI预警系统(源码+论文+部署讲解等)

需要源代码&#xff0c;演示视频&#xff0c;ppt设计原稿资料&#xff0c;请文末卡片联系 !](https://i-blog.csdnimg.cn/direct/242d53cd069940b5b7a6db2bb031d406.png#pic_center)

【神经网络】python实现神经网络(三)——正向学习的模拟演练

有了之前的经验(【神经网络】python实现神经网络(二)——正向推理的模拟演练),我们继续来介绍如何正向训练神经网络中的超参(包含权重以及偏置),本章大致的流程图如下: 一.损失函数 神经网络以某个指标为基准寻求最优权重参数,而这个指标即可称之为 “损失函数” 。(…

分区格式变RAW故障深度解析与数据恢复实战指南‌

分区格式变RAW的本质‌ 当存储设备&#xff08;如硬盘、U盘或移动硬盘&#xff09;的分区突然显示为RAW格式时&#xff0c;意味着操作系统无法识别其原有的文件系统结构&#xff08;如NTFS、FAT32等&#xff09;。此时&#xff0c;用户访问该分区会提示“需要格式化”或直接显示…

【QT】Qt5 QtWebEngine使用教程

目录 1、QtWebEngine相比于QtWebKit的优势2、项目配置2.1 确认 Qt 版本2.2 在.pro 文件中添加依赖3、显示网页4、实现Qt和网页JavaScript之间的交互4.1 Qt执行网页的JavaScript代码4.2 JavaScript调用Qt对象的函数QtWebEngine 是 Qt 框架中用于在应用程序中嵌入 Web 内容的模块…

网络安全-等级保护(等保) 1-0 等级保护制度公安部前期发文总结

################################################################################ 等级保护从1994年开始已经有相关文件下发&#xff0c;进行建设&#xff0c;后续今年多年制度完善&#xff0c;现在已进入等保2.0时代&#xff0c;相关政策已运行多年。 前期等保相关发文&…

视图函数的应用

1.实现将当前日期和时间编码为HTML文档并返回的简单视图函数 文章目录 1.实现将当前日期和时间编码为HTML文档并返回的简单视图函数1.1打开visualcode 按图示点击 创建新的终端1.2然后定义ViewDjango项目根目录下的路由文件urls.py&#xff0c;实现到SimpleView应用的路由路径1…