【多线程进阶】常见的锁策略

news2025/7/7 6:31:29

文章目录

  • 前言
  • 1. 乐观锁 vs 悲观锁
  • 2. 轻量级锁 vs 重量级锁
  • 3. 自旋锁 vs 挂起等待锁
  • 4. 读写锁 vs 互斥锁
  • 5. 公平锁 vs 非公平锁
  • 6. 可重入锁 vs 不可重入锁
  • 总结


前言

本章节所讲解的锁策略不仅仅是局限于 Java . 任何和 “锁” 相关的话题, 都可能会涉及到以下内容. 这些特性主要是给锁的实现者来参考的.

本文中讲解的锁, 并不是指某个具体的锁, 而是一个抽象的概念, 描述的是 “一类锁”. 即使是普通的程序猿也需要了解一些, 对于合理的使用锁也是有很大帮助的.

关注收藏, 开始学习吧🧐


1. 乐观锁 vs 悲观锁

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

悲观锁:
预测该场景中, 非常容易出现锁冲突. 总是假设最坏的情况, 每次去拿数据的时候都认为别人会修改, 所以每次在拿数据的时候都会上锁, 这样别人想拿这个数据就会阻塞直到它拿到锁.

锁冲突: 指两个线程尝试去获取一把锁, 一个线程获取成功, 则另一个线程就会阻塞等待, 这就是锁冲突.

2. 轻量级锁 vs 重量级锁

锁的核心特性 “原子性”, 这样的机制追根溯源是 CPU 这样的硬件设备提供的.

  • CPU 提供了 “原子操作指令”.
  • 操作系统基于 CPU 的原子指令, 实现了 mutex 互斥锁.
  • JVM 基于操作系统提供的互斥锁, 实现了 synchronizedReentrantLock 等关键字和类.
    在这里插入图片描述
    注意, synchronized 并不仅仅是对 mutex 进行封装, 在 synchronized 内部还做了很多其他的 工作

轻量级锁:
加锁机制尽可能的不使用 mutex, 而是尽量在用户态代码完成. 加锁开销比较小, 花费时间少, 占用资源较少.

重量级锁:
加锁机制重度依赖 OS 提供的 mutex. 加锁开销比较大, 花费时间多, 占用资源较多.

注意:

  • 一个乐观锁, 很可能是一把轻量级锁. 而一个悲观锁, 很可能是一把重量级锁. (并不绝对)
  • 悲观乐观, 是在加锁之前, 对锁冲突概率的一个预测, 决定之后工作的多少. 而重量轻量, 是在加锁之后, 考量实际的锁的开销.
  • 正是因为概念有些重合, 在针对某个具体的锁时, 可能把它叫做乐观锁, 也可能叫做轻量级锁.

3. 自旋锁 vs 挂起等待锁

挂起等待锁:
挂起等待锁, 是重量级锁的一种典型实现. 通过内核态, 借助系统提供的锁机制, 当出现锁冲突的时候, 会牵扯到内核对于线程的调度. 将冲突的线程挂起 (阻塞等待).

自旋锁:
自旋锁, 是轻量级锁的一种典型实现. 在用户态下, 通过自旋的方式 (while 循环), 实现类似于加锁的效果.

自旋锁伪代码:

while (抢锁(lock) == 失败) {}
  • 优点: 没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁.
  • 缺点: 如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源. (而挂起等待的时候是
    不消耗 CPU 的, 但无法第一时间获取到锁).

4. 读写锁 vs 互斥锁

多线程之间, 数据的读取方之间不会产生线程安全问题, 但数据的写入方互相之间以及和读者之间都需要进行互斥. 如果两种场景下都用同一个锁, 就会产生极大的性能损耗. 所以读写锁因此而产生.

读写锁 (readers-writer lock), 顾名思义, 在执行加锁操作时需要额外表明读写意图, 复数读者之间并不互斥, 而写者则要求与任何人互斥.

一个线程对于数据的访问, 主要存在两种操作: 读数据写数据.

  • 两个线程都只是读一个数据, 此时并没有线程安全问题. 直接并发的读取即可.
  • 两个线程都要写一个数据, 有线程安全问题.
  • 一个线程读, 另外一个线程写, 也会有线程安全问题.

读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁.

  • ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行加锁解锁.
  • ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁.

其中,

  • 读加锁和读加锁之间, 不互斥.
  • 写加锁和写加锁之间, 互斥.
  • 读加锁和写加锁之间, 互斥.

而在实际开发中, 读操作出现的频率, 往往比写操作要高得多. 在该场景中, 使用读写锁的话, 就可以尽可能的避免产生锁竞争, 此时, 多线程并发执行的效率就会更高. 而如果使用互斥锁的话, 就会产生不必要的挂起等待, 这就是前者读写锁存在的意义.

5. 公平锁 vs 非公平锁

假设有三个线程 A, B, C.
A 先尝试获取锁, 获取成功. 然后 B 再尝试获取锁, 获取失败, 阻塞等待. 然后C 也尝试获取锁, C 也获取失败, 也阻塞等待.
当线程 A 释放锁的时候, 会发生什么呢?

公平锁:
遵守 “先来后到”. B 比 C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.

非公平锁:
不遵守 “先来后到”. B 和 C 都有可能获取到锁. 谁先拿到锁就是谁的.

注意:

  • 操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是非公平锁. 如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序.
  • 公平锁和非公平锁没有好坏之分, 关键还是看适用场景.

6. 可重入锁 vs 不可重入锁

可重入锁:
可重入锁的字面意思是 “可以重新进入的锁”, 即允许同一个线程多次获取同一把锁.
比如一个递归函数里有加锁操作, 递归过程中这个锁会阻塞自己吗? 如果不会, 那么这个锁就是可重入锁 (因为这个原因可重入锁也叫做递归锁).

不可重入锁:
按照之前对于锁的设定, 第二次加锁的时候, 就会阻塞等待. 直到第一次的锁被释放, 才能获取到第二个锁. 但是释放第一个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无法进行解锁操作. 这时候就会产生死锁. 这个锁就是不可重入锁.

那么, 死锁具体是什么一个什么样的情况呢? 我们在下一篇博文详细讲述.


总结

✨ 本文主要讲述了锁的几个主要策略. 主要讲解了几大锁策略的概念, 以及其所对应的场景.
✨ 想了解更多的多线程知识, 可以收藏一下本人的多线程学习专栏, 里面会持续更新本人的学习记录, 跟随我一起不断学习.
✨ 感谢你们的耐心阅读, 博主本人也是一名学生, 也还有需要很多学习的东西. 写这篇文章是以本人所学内容为基础, 日后也会不断更新自己的学习记录, 我们一起努力进步, 变得优秀, 小小菜鸟, 也能有大大梦想, 关注我, 一起学习.

再次感谢你们的阅读, 你们的鼓励是我创作的最大动力!!!!!

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

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

相关文章

windows系统一键开启和关闭虚拟化

说明 跟虚拟化相关的三个程序 一键开启脚本 REM 开启 Hyper-V 服务 pushd "%~dp0"dir /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hyper-v.txtfor /f %%i in (findstr /i . hyper-v.txt 2^>nul) do dism /online /norestart /add-package:"%Sy…

一步一招,教你如何制作出成功的优惠促销微传单

在当今的数字化时代,几乎所有的事情都可以在互联网上完成,包括制作宣传单。有很多在线工具可以帮助我们轻松制作出精美的商场促销宣传单。下面就以乔拓云为例,详细介绍如何简单几步制作出让人眼前一亮的商场促销宣传单。 1. 注册并登录乔拓云…

WPS Office for Linux即将面临开源

WPS Office 是一款免费(但不开源)的办公套件,目前已经在 Windows、macOS、Android、iOS 和 Linux 设备上线,由于在界面和功能上模仿了微软 Office 的部分特性,对于那些轻量办公的用户来说已经能够完全驾驭大部分需求。…

三、飞行和射击

目录 1.飞行的实现 2.限制玩家视角 3.射击的实现 4.附录 1.飞行的实现 (1)在Player预制体上挂载Configuration Joint组件,并修改其Y Drive属性 (2) 修改PlayerInput.cs和PlayerController.cs以实现飞行 PlayerIn…

【刷题笔记10.2】LeetCode: 罗马数字转整数

LeetCode: 罗马数字转整数 一、题目描述 二、分析 方法一: 将给定字符串s中的"IV", “IX”, “XL”, “XC”, “CD”, “CM” 全部替换为其他字符如:a, b, c, d, e, f 这种,然后就可以遍历累加了。 s s.replace("IV",…

【数据代理+事件处理+计算属性与监视+绑定样式+条件渲染】

数据代理事件处理计算属性与监视绑定样式条件渲染 1 数据代理1.1 回顾Object.defineProperty方法1.2 数据代理 2 事件处理2.1 绑定监听2.2 事件修饰符2.3 键盘事件 3 计算属性与监视3.1 计算属性3.2 监视属性(侦视属性)3.3 watch对比computed 4 绑定样式4.1 绑定class样式4.2 绑…

Linux命令(一)(目录相关)

目录可以更快找到你想要的命令 1. 命令入门2. 常用目录命令(cd、pwd、ls、cp、rm、mkdir)*cd:用来进入到指定的文件夹*常见操作关于绝对路径和相对路径的说明: *pwd:显示当前工作目录的路径*选项用例 *ls:显示目录中文件及其属性信…

数据结构与算法-(7)---栈的应用-(3)表达式转换

🌈write in front🌈 🧸大家好,我是Aileen🧸.希望你看完之后,能对你有所帮助,不足请指正!共同学习交流. 🆔本文由Aileen_0v0🧸 原创 CSDN首发🐒 如…

区块链(8):p2p去中心化之websoket服务端实现业务逻辑

1 业务逻辑 例如 peer1和peer2之间相互通信 peer1通过onopen{ write(Mesage(QUERY_LATEST))} 向peer2发送消息“我要最新的区块”。 peer2通过onMessage收到消息,通过handleMessage方法对消息进行处理。 handleMessage根据消息类型进行处理 RESPONSE_BLOCKCHAIN:返回区块链…

WSL2和ubuntu的安装过程

目录 1.WSL2的安装 2.Ubuntu的安装 3.安装完成后的打开方式 1.WSL2的安装 按下WINX键,选择Windows PowerShell (管理员) 1.1执行以下命令,该命令的作用是:启用适用于 Linux 的 Windows 子系统 dism.exe /online /enable-feature /featur…

BI神器Power Query(25)-- 使用PQ实现表格多列转换(1/3)

实例需求:原始表格包含多列属性数据,现在需要将不同属性分列展示在不同的行中,att1、att3、att5为一组,att2、att3、att6为另一组,数据如下所示。 更新表格数据 原始数据表: Col1Col2Att1Att2Att3Att4Att5Att6AAADD…

经典网络解析(四) transformer | 自注意力、多头、发展

文章目录 1 背景1.1 困境1.2 基本架构 2 嵌入层3 编码器部分3.1 自注意力层3.2 多头注意力机制3.3 LayerNorm归一化层 4 解码器5 transformer的发展6 代码 1 背景 1.1 困境 transformer可以并行训练,也是用来实现attention注意力机制 之前RNN的困境 &#xff08…

【PostgreSQL】【存储管理】表和元组的组织方式

外存管理负责处理数据库与外存介质(PostgreSQL8.4.1版本中只支持磁盘的管理操作)的交互过程。在PostgreSQL中,外存管理由SMGR(主要代码在smgr.c中)提供了对外存的统一接口。SMGR负责统管各种介质管理器,会根据上层的请求选择一个具体的介质管理器进行操作…

【最优化理论】线性规划标准模型的基本概念与性质

我们在中学阶段就遇到过线性规划问题,主要是二维的情况,而求解的方法一般是非常直观、高效的图解法。根据过往的经验,线性规划问题的最优目标值一般在可行域的顶点处取得,那么本文就对这个问题进行更深入的探讨,维度也…

找不到msvcp140.dll解决方法的5个解决方法以及msvcp140.dll丢失原因分析

msvcp140.dll 是 Microsoft Visual C 2017 Redistributable 的一部分,许多应用程序和游戏都需要这个动态链接库(DLL)才能正常运行。如果您的系统中找不到 msvcp140.dll,您可能会遇到无法打开某些应用程序或游戏的困境。小编将讨论…

运用动态内存实现通讯录(增删查改+排序)

目录 前言: 实现通讯录: 1.创建和调用菜单: 2.创建联系人信息和通讯录: 3.初始化通讯录: 4.增加联系人: 5.显示联系人: 6.删除联系人: ​编辑 7.查找联系人: ​…

nodejs+vue健身服务应用elementui

第三章 系统分析 10 3.1需求分析 10 3.2可行性分析 10 3.2.1技术可行性:技术背景 10 3.2.2经济可行性 11 3.2.3操作可行性: 11 3.3性能分析 11 3.4系统操作流程 12 3.4.1管理员登录流程 12 3.4.2信息添加流程 12 3.4.3信息删除流程 13 第四章 系统设计与…

AWS Lambda Golang HelloWorld 快速入门

操作步骤 以下测试基于 WSL2 Ubuntu 22.04 环境 # 下载最新 golang wget https://golang.google.cn/dl/go1.21.1.linux-amd64.tar.gz# 解压 tar -C ~/.local/ -xzf go1.21.1.linux-amd64.tar.gz# 配置环境变量 PATH echo export PATH$PATH:~/.local/go/bin >> ~/.bashrc …

【小沐学前端】Node.js实现基于Protobuf协议的WebSocket通信

文章目录 1、简介1.1 Node1.2 WebSocket1.3 Protobuf 2、安装2.1 Node2.2 WebSocket2.2.1 nodejs-websocket2.2.2 ws 2.3 Protobuf 3、代码测试3.1 例子1:websocket(html)3.1.1 客户端:yxy_wsclient1.html3.1.2 客户端&#xff1a…

绘制动图,金星木星月亮太阳绕圆

图💫 input绘制 行星 木星 太阳 地球 金星💫 地球 月亮各自旋转 1年 角度 360.gif import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation import math import os# 设置中文字体 font_style 宋体 plt.rcParam…