总结常见的六大锁策略

目录

一、乐观锁vs悲观锁

1、引入:

2、定义:

3、区别及使用场景

二、读写锁 

 1、引入

 2、定义

3、适用场景

三、重量级锁 vs 轻量级锁

1、概念

四、自旋锁(Spin Lock) 

1、引入 

2、区分

五、公平锁 vs 非公平锁

1、概念 

2、synchronized 是非公平锁 

六、可重入锁 vs 不可重入锁 

1、引入 

2、synchronized 是可重入锁 

🎊总结:synchronized特点:


一、乐观锁vs悲观锁🎊

1、引入:

举个栗子: 同学 A 和 同学 B 想请教老师一个问题.

    同学 A 认为 "老师是比较忙的, 我来问问题, 老师不一定有空解答". 因此同学 A 会先给老师发消息: "老师 你忙嘛? 我下午两点能来找你问个问题嘛?" (相当于加锁操作) 得到肯定的答复之后, 才会真的来问问题. 如果得到了否定的答复, 那就等一段时间, 下次再来和老师确定时间. 这个是悲观锁.

    同学 B 认为 "老师是比较闲的, 我来问问题, 老师大概率是有空解答的". 因此同学 B 直接就来找老师.(没 加锁, 直接访问资源) 如果老师确实比较闲, 那么直接问题就解决了. 如果老师这会确实很忙, 那么同学 B 也不会打扰老师, 就下次再来(虽然没加锁, 但是能识别出数据访问冲突). 这个是乐观锁.

2、定义:

悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这 样别人想拿这个数据就会阻塞直到它拿到锁。

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

这两种思路不能说谁优谁劣, 而是看当前的场景是否合适. 如果当前老师确实比较忙, 那么使用悲观锁的策略更合适, 使用乐观锁会导致 "白跑很多趟", 耗费额 外的资源. 如果当前老师确实比较闲, 那么使用乐观锁的策略更合适, 使用悲观锁会让效率比较低. 

3、区别及使用场景

Synchronized 初始使用乐观锁策略. 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略. 总的来说,

乐观锁适合于并发写入比较少、并发读取比较多的场景,可以提高系统的并发性能;

而悲观锁适合于并发写入比较频繁的场景,可以保证数据的一致性和完整性。选择合适的锁策略取决于具体的应用场景和需求。

二、读写锁 🎊

 1、引入

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

那么我们可以得出以下结论: 

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

 2、定义

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

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

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

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

其中,

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

3、适用场景

读写锁特别适合于 "频繁读, 不频繁写" 的场景中. (这样的场景其实也是非常广泛存在的).

比如比特的教务系统. 每节课老师都要使用教务系统点名, 点名就需要查看班级的同学列表(读操作). 这个操作可能要每周 执行好几次. 而什么时候修改同学列表呢(写操作)?

就新同学加入的时候. 可能一个月都不必改一次.

再比如, 同学们使用教务系统查看作业(读操作), 一个班级的同学很多, 读操作一天就要进行几十次 上百次. 但是这一节课的作业, 老师只是布置了一次(写操作) 

注意:Synchronized 不是读写锁 

三、重量级锁 vs 轻量级锁🎊

   主要在于是否依赖了 OS 提供了 mutex(内核态和用户态的切换频率)

1、概念

重量级锁: 加锁机制重度依赖了 OS 提供了 mutex 大量的内核态用户态切换 很容易引发线程的调度 这两个操作, 成本比较高. 一旦涉及到用户态和内核态的切换, 就意味着 "沧海桑田".

轻量级锁: 加锁机制尽可能不使用 mutex, 而是尽量在用户态代码完成. 实在搞不定了, 再使用 mutex. 少量的内核态用户态切换. 不太容易引发线程调度. 

锁的核心特性 "原子性", 这样的机制追根溯源是 CPU 这样的硬件设备提供的. CPU 提供了 "原子操作指令". 操作系统基于 CPU 的原子指令, 实现了 mutex 互斥锁. JVM 基于操作系统提供的互斥锁, 实现了 synchronized 和 ReentrantLock 等关键字和类.

理解用户态 vs 内核态

想象去银行办业务.

在窗口外, 自己做, 这是用户态. 用户态的时间成本是比较可控的. 在窗口内, 工作人员做, 这是内核态. 内核态的时间成本是不太可控的. 如果办业务的时候反复和工作人员沟通, 还需要重新排队, 这时效率是很低的. 

synchronized 开始是一个轻量级锁. 如果锁冲突比较严重, 就会变成重量级锁.

四、自旋锁(Spin Lock)🎊 

1、引入 

按之前的方式,线程在抢锁失败后进入阻塞状态,放弃 CPU,需要过很久才能再次被调度. 但实际上, 大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。没必要就放弃 CPU. 这个 时候就可以使用自旋锁来处理这样的问题. 

如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会 在极短的时间内到来. 一旦锁被其他线程释放, 就能第一时间获取到锁.

理解自旋锁 vs 挂起等待锁

想象一下, 去追求一个女神. 当男生向女神表白后, 女神说: 你是个好人, 但是我有男朋友了~~ 挂起等待锁: 陷入沉沦不能自拔.... 过了很久很久之后, 突然女神发来消息, "咱俩要不试试?" (注意, 这个很长的时间间隔里, 女神可能已经换了好几个男票了). 自旋锁: 死皮赖脸坚韧不拔. 仍然每天持续的和女神说早安晚安. 一旦女神和上一任分手, 那么就能 立刻抓住机会上位. 

2、区分

自旋锁是一种典型的 轻量级锁 的实现方式.

优点: 没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁.

缺点: 如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源. (而挂起等待的时候是 不消耗 CPU 的).

synchronized 中的轻量级锁策略大概率就是通过自旋锁的方式实现的. 

五、公平锁 vs 非公平锁🎊

1、概念 

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

非公平锁: 不遵守 "先来后到". B 和 C 都有可能获取到锁. 

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

2、synchronized 是非公平锁 

六、可重入锁 vs 不可重入锁 🎊

1、引入 

可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。

比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入 锁(因为这个原因可重入锁也叫做递归锁)。

Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括 synchronized关键字锁都是可重入的。 而 Linux 系统提供的 mutex 是不可重入锁 

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

2、synchronized 是可重入锁 

总结:synchronized特点:

  1. 既是乐观锁,也是悲观锁
  2. 即时轻量级锁,又是重量级锁
  3. 轻量级锁基于自旋实现,重量级锁基于挂起等待实现
  4. 不是读写锁
  5. 是可重入锁
  6. 是非公平锁 

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值