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

文章目录

  • 前言
  • 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.rhkb.cn/news/146152.html

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

相关文章

如何初始化一个vue项目

如何初始化一个vue项目 安装 vue-cli 后 ,终端执行 vue ui npm install vue-cli --save-devCLI 服务 | Vue CLI (vuejs.org) 等一段时间后 。。。 进入项目仪表盘 设置其他模块 项目构建后目录 vue.config.js 文件相关配置 官方vue.config.js 参考文档 https://cli.vuejs.o…

Linux基础指令(六)

目录 前言1. man 指令2. date 指令3. cal 指令4. bc 指令5. uname 指令结语: 前言 欢迎各位伙伴来到学习 Linux 指令的 第六天!!! 在上一篇文章 Linux基本指令(五) 中,我们通过一段故事线,带大家感性的了…

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

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

通过containerd部署k8s集群环境及初始化时部分报错解决

目录 一.基础环境配置(每个节点都做) 1.hosts解析 2.防火墙和selinux 3.安装基本软件并配置时间同步 4.禁用swap分区 5.更改内核参数 6.配置ipvs 7.k8s下载 (1)配置镜像下载相关软件 (2)配置kube…

8.3Jmeter使用json提取器提取数组值并循环(循环控制器)遍历使用

Jmeter使用json提取器提取数组值并循环遍历使用 响应返回值例如: {"code":0,"data":{"totalCount":11,"pageSize":100,"totalPage":1,"currPage":1,"list":[{"structuredId":&q…

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 第四章 系统设计与…

使用docker完成minio服务部署扩容备份迁移生产实践文档

一、minio服务扩容方案 当服务器存储空间不足的时候,需要进行扩容,扩容过程中需要短暂停机时间,预计在一小时内能够完成和恢复 统一注意事项 强烈建议为部署中的所有节点选择基本相似的硬件配置。确保硬件(CPU、内存、主板、存…

什么是物联网智慧公厕?

在当今科技快速发展的背景下,具备全感知、可靠传输、智能处理三大特点的物联网技术,正逐渐渗透到各个领域。而智慧公厕作为其中的一个创新应用,正逐渐受到市场的关注和重视。 什么是物联网智慧公厕?物联网智慧公厕是指通过物联网…

SmartX 边缘计算解决方案:简单稳定,支持各类应用负载

在《一文了解近端边缘 IT 基础架构技术需求》文章中,我们为大家分析了边缘应用对 IT 基础架构的技术要求,以及为什么超融合架构是支持边缘场景的最佳选择。值得一提的是,IDC 近日发布的《中国软件定义存储(SDS)及超融合…

Eclipse 主网即将上线迎空投预期,Zepoch 节点或成受益者?

目前,Zepoch节点空投页面中,模块化Layer2 Rollup项目Eclipse出现在其空投列表中。 配合近期Eclipse宣布了其将由SVM提供支持的Layer2主网架构,并将在今年年底上线主网的消息后,不免引发两点猜测:一个是Eclipse或将在不…

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

数据代理事件处理计算属性与监视绑定样式条件渲染 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 绑…

[尚硅谷React笔记]——第2章 React面向组件编程

目录: 基本理解和使用: 使用React开发者工具调试函数式组件复习类的基本知识类式组件组件三大核心属性1: state 复习类中方法this指向: 复习bind函数:解决changeWeather中this指向问题:一般写法:state.htm…

进程之间的通信方式(共享存储,消息传递,管道通信)

进程通信 进程间通信(Inter-Process Communication,IPC)是指两个进程之间产生数据交互。进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立。为了保证安全,一个进程不能直接访问另一…

前端开发和后端开发的一些建议

前端开发和后端开发是Web开发的两个方向 前端开发主要负责实现用户在浏览器上看到的界面和交互体验,包括HTML、CSS和JavaScript等技术。后端开发主要负责处理服务器端的逻辑和数据,包括数据库操作、服务器配置和接口开发等技术。 前端开发 前端开发需…

Python-Flask:编写自动化连接demo脚本:v1.0.0

主函数: # _*_ Coding : UTF-8 _*_ # Time : 13:14 # Author : YYZ # File : Flask # Project : Python_Project_爬虫 import jsonfrom flask import Flask,request,jsonify import sshapi Flask(__name__)# methods: 指定请求方式 接口解析参数host host_info[…

产品经理如何科学的进行需求调研?

导语:作为产品经理,需求调研是开展工作的重要环节之一。科学、有效地进行需求调研不仅可以帮助产品经理更好地了解用户需求,还能指导产品设计和功能开发,提升产品的竞争力。本文将介绍几种科学的方法和技巧,帮助产品经…

【接口技术】总线课堂习题

1:CPU在执行OUT DX, AL指令时,()寄存器的内容送到地址总线上 A,DL B,DX C,AX D,DL 解答:B out指令是把AL的数据输出到DX的端口,因此AL寄存器的内容送到…

C++17中std::filesystem::directory_entry的使用

C17引入了std::filesystem库(文件系统库, filesystem library)。这里整理下std::filesystem::directory_entry的使用。 std::filesystem::directory_entry,目录项,获取文件属性。此directory_entry类主要用法包括: (1).构造函数、…

虚拟车衣VR云展厅平台扩大了展览的触达范围

传统展厅主要是以静态陈列的形式来传达内容,主要的展示形式有图片、视频等,具有一定的局限性,体验感较差,客户往往不能深入地了解信息和细节内容。 VR全景看车是通过虚拟现实技术实现逼真的汽车观赏和试乘体验。消费者可以通过智能…

嵌入式Linux应用开发-基础知识-第十八章系统对中断的处理②

嵌入式Linux应用开发-基础知识-第十八章系统对中断的处理② 第十八章 Linux系统对中断的处理 ②18.3 Linux中断系统中的重要数据结构18.3.1 irq_desc数组18.3.2 irqaction结构体18.3.3 irq_data结构体18.3.4 irq_domain结构体18.3.5 irq_chip结构体 18.4 在设备树中指定中断_在…