【多线程】常见的锁策略

文章目录

    • [1. 乐观锁vs悲观锁](#1. 乐观锁vs悲观锁)
      • [1.1 乐悲概念对比](#1.1 乐悲概念对比)
      • [1.2 乐观锁机制](#1.2 乐观锁机制)
    • [2. 读写锁](#2. 读写锁)
    • [3. 轻量级锁vs重量级锁](#3. 轻量级锁vs重量级锁)
      • [3.1 锁](#3.1 锁)
      • [3.2 轻量级锁](#3.2 轻量级锁)
      • [3.3 重量级锁](#3.3 重量级锁)
    • [4. 公平锁vs非公平锁](#4. 公平锁vs非公平锁)
    • [5. 可重入锁vs不可重入锁](#5. 可重入锁vs不可重入锁)
    • [6. 死锁](#6. 死锁)

1. 乐观锁vs悲观锁

1.1 乐悲概念对比

乐观锁:在乐观锁中会假设共享资源并没有加锁,对其进行访问时,直接访问,如果有其他线程对其访问,则会放弃此次访问,等待一定再次访问(乐观锁虽然没有加锁,但是可以识别出数据冲突,下面讲)。一般情况下,每次访问时间间隔会加大,适用于并发冲突较小或不发生的环境中。

悲观锁:悲观锁中访问共享资源,每次都会加锁,其他线程访问时,会进行堵塞挂起。适合用于并发冲突大的环境中。

两者并没有谁优谁劣,在不同情境下有不同优势,如图:

如果老师不忙,显然乐观锁效率高;

如果老师忙,悲观锁效率高;

而synchronized初始使用的就是乐观锁,当锁竞争比较激烈时,就会转换为悲观锁。

1.2 乐观锁机制

乐观锁并没有直接加锁,而是引入了一个版本号或者时间戳的东西,用来检测数据冲突。当一个线程访问共享数据后,会生成一个版本号的东西,如果期间又其他线程对其访问,版本号会发生改变,则第一个线程再运行时,版本号会对不上,那么,第一个线程会通过重新读取数据、合并更改或者放弃修改来解决。

乐观锁执行顺序:

  1. 读取数据:线程或进程读取要修改的数据,生成版本号或时间戳;
  2. 修改数据:对数据进行修改;
  3. 检查冲突:在修改完成后,乐观地认为没有并发冲突,然后尝试提交数据修改。在这个阶段,系统会比较当前数据的版本号或时间戳与线程最初读取的版本号或时间戳是否一致。
  4. 处理冲突:如果在提交时发现其他线程已经修改了数据(版本号或时间戳不一致),则需要处理冲突。通常,这可以通过重新读取数据、合并更改或者放弃修改来解决。

2. 读写锁

多线程之间,读(访问)数据与读数据之间并不会有线程安全,但是读数据与写(修改)数据之间却会,我们如果把两者放入一个锁中,对性能便会造成损耗,这个时候读写锁便孕育而生了。

读写锁(readers-writer lock),在执行加锁操作时会额外表名读写意图,那么读与读之间便不会互斥,写与任何操作都互斥。适用于对数据频繁的读而不是改的情景中。

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

锁.

ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行

加锁解锁.

ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进

行加锁解锁.

而synchronized不是读写锁。

3. 轻量级锁vs重量级锁

3.1 锁

加锁的核心就时"原子性",这样的机制其实时CPU这样的设备提供的。

3.2 轻量级锁

轻量级锁如其名是一种轻量级的锁,是减小锁开销的一种机制,加锁机制一般都在代码层面完成,尽量不使用mutex。当一个线程在访问共享资源的时候,可以更容易的获得锁,不需要等待堵塞。

轻量级锁主要包括两种状态:

  1. 自旋锁:当多个线程竞争同一把锁时,不会立即堵塞,而会通过在一定次数的自旋中尝试获得锁,减少线程转换的成本。减少线程调度。
  2. 偏向锁:只有一个线程访问共享资源的情况,它允许该线程获得锁(这个锁也不是真正的锁,而是打上了一个类似锁的标记。如果后面有其他线程访问此资源,则会真正的加锁,反之就不会加锁)而无需竞争,减少了锁的开销。

3.3 重量级锁

重量级锁用到了OS提供的mutex,里面涉及了大量的内核态用户态切换,很容易引起线程的调度。在多个线程竞争同一把锁时,会造成线程堵塞。

两者也不分谁优谁劣,轻量级锁适用于多线程竞争不激烈的情况,可以减小锁的开销,而重量级锁适用于需要确保强一致性的高并发场景,但性能开销较大。不同的编程语言和运行时环境可能会采用不同的锁机制,以满足不同应用场景的需求。

4. 公平锁vs非公平锁

公平锁就是当多个线程访问共同资源时,当锁释放后,会按照先后等待的先后顺序获得锁,先来后到,锁里需要含特定的等待队列顺序,所以需要更大的开销。

不公平锁就是,当锁释放后,并不知道哪个线程会获得锁,是随机的。

两者并没有优劣之分。

synchronized就是一种非公平锁。

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

可重入锁就是在一个锁中,可以再进行一次加锁,不会发生死锁,这时只有两个锁都释放,其他线程才能获得此锁。

不可重入锁,就是当一个锁中再加一个锁就会发生死锁,线程无休止的堵塞。

synchronized就是一个可重入锁。

6. 死锁

死锁是在多线程或多进程并发程序中的一种常见问题,它发生在两个或多个线程(或进程)相互等待对方释放所需资源的情况下,导致它们都无法继续执行,从而陷入无限等待的状态。

死锁产生的四个必要条件:

  1. 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用;
  2. 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放;
  3. 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有;
  4. 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
相关推荐
jackletter9 分钟前
linux:安装 ubuntu 26.04.1
linux·ubuntu·ubuntu26.04.1
梦帮科技18 分钟前
LoRA / QLoRA 低秩矩阵流形更新:SVD 分解、秩与缩放因子 α 的物理意义与梯度稳定性保障
网络·深度学习·神经网络·线性代数·矩阵·架构·数据挖掘
2401_8697695918 分钟前
make 进度条
linux
阿钱真强道24 分钟前
28 嵌入式操作系统 | 网关加 TCP 命令通道:粘包/半包怎么切
网络·网络协议·tcp/ip·粘包
HY小宝F1 小时前
树莓派智能摄像头系统 30 天重学计划之 Day 03 · 网络基础:建立“包“的思维 —— 学习总结
网络·学习
ayaya_mana1 小时前
MCTier 组网联机工具:安装、配置与私有化部署
网络·windows·内网穿透·联机
故七月1 小时前
GEO 信源评估体系:如何判断一条网页信源能否被大模型采信
前端·网络·人工智能
hasty1 小时前
一次 WebSocket 握手为何能终止进程?Undici CVE-2026-19534 的异常边界
网络·websocket·网络协议
C++ 老炮儿的技术栈1 小时前
有符号变量与无符号变量的区别
服务器·开发语言·前端·数据结构·c++·算法·c
实点科技2 小时前
XB6S-EI2002如何与信捷XDH-60T4-E实现EtherNet/IP通讯?
网络