【多线程】常见的锁策略

文章目录

    • [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的资源。这样就形成了一个等待环路。
相关推荐
蛊明44 分钟前
下载CentOS 10
linux·运维·centos
北京-宏哥1 小时前
Linux系统安装MySQL5.7(其他版本类似)避坑指南
linux·运维·服务器
靖节先生1 小时前
Wireshark详解
网络·测试工具·wireshark
Aphelios3801 小时前
Linux 下 VIM 编辑器学习记录:从基础到进阶(下)
java·linux·学习·编辑器·vim
qw9491 小时前
Linux 高级篇 日志管理、定制自己的Linux系统、备份与恢复
linux·运维·服务器
丶只有影子1 小时前
【Nacos】从零开始启动Nacos服务(windows/linux)
linux·运维·windows·微服务·springcloud
-SGlow-2 小时前
Linux相关概念和易错知识点(30)(线程互斥、线程同步)
linux·运维·服务器
技术小齐2 小时前
网络运维学习笔记 021 HCIA-Datacom新增知识点02 SDN与NFV概述
运维·网络·学习
茂茂在长安3 小时前
Linux 命令大全完整版(11)
java·linux·运维·服务器·前端·centos
songbaoxian3 小时前
ElasticSearch
java·linux·elasticsearch