javaEE 初阶 — 常见的锁策略

文章目录

  • 1. 乐观锁 vs 悲观锁
  • 2. 互斥锁 vs 读写锁
  • 3. 重量级锁 vs 轻量级锁
  • 4. 自旋锁 vs 挂起等待锁
  • 5. 公平锁 vs 非公平锁
  • 6. 可重入锁 vs 不可重入锁
  • 7. synchronized 是哪种锁
  • 8. 相关面试题

1. 乐观锁 vs 悲观锁


乐观锁 指的是预测锁竞争不是很激烈,也就是指这里做的工作相对较少。
悲观锁 指的是预测锁竞争会很激烈,也就是指指这里做的工作相对多一点。

悲观和乐观都不是绝对的,主要就是看预测锁竞争激烈程度的结论。


这两个锁是站在冲突概率的预测角度。

2. 互斥锁 vs 读写锁


互斥锁 就是之前使用的 synchronized 这样的锁,提供 加锁 和 解锁 两个操作。
如果一个线程加锁了,另一个线程也尝试加锁,就会阻塞等待。

读写锁 提供了三种操作:

  • 针对读加锁
  • 针对写加锁
  • 解锁

基于一个事实:

多线程针对同一个变量并发读,这个时候是没有线程安全问题的,也不需要加锁控制。
读写锁 就是针对这种情况所采取的特殊处理。

当前的代码中,如果只是读操作,就加读锁即可,如果有写操作,就加写锁。

  • 读锁读锁 之间没有互斥
  • 写锁写锁 之间存在互斥
  • 写锁读锁 之间存在互斥

如果当前有一组线程都去读(加读锁),此时这些线程之间是没有锁竞争和线程安全问题的。
如果当前有一组操作有读也有写,才会有竞争问题。

另一个事实:

在很多开发场景中,读操作的频率比写操作的频率高很多。

3. 重量级锁 vs 轻量级锁


轻量级锁 加锁解锁开销比较小,效率更高;重量级锁 加锁解锁开销比较大,效率更低。

多数情况下,乐观锁也是一个轻量级锁(不能完全保证)
多数情况下,悲观锁也是一个重量级锁(不能完全保证)

这两个锁是站在加锁操作的开销角度。

4. 自旋锁 vs 挂起等待锁


自旋锁 是一种典型的轻量级锁,挂起等待锁 是一种典型的重量级锁。

举一个例子

张三要向一个女生表白,此时就是张三尝试对这个女生加锁。
但是女生说她有男友了,也就是说明她被别人加锁了。

如果张三还不死心,为了找一个机会上位,此时就有两种等待的方式。

  • 自旋锁:
    张三没有都在女生身边转悠,一旦女生分手了,就能第一时间知道。
    而一旦锁被释放就能第一时间感知到,从而有机会获取到锁,很明显,自旋锁占用了大量的资源

  • 挂起等待锁:
    张三愿意等女生分手,张三就悄悄地等着,也不会去打扰那个女生。
    当女生分手的时候,有可能会想起张三,也有可能不会。但是更大的概率,女生会把张三也忘记了。
    当真的被唤醒的时候,不知道过了多长时间。
    此时就是把 CPU 省下来了,就可以干别的事情了。

5. 公平锁 vs 非公平锁


此处的公平定义为 “先来后到”

举个例子

javaEE 初阶 — 常见的锁策略_第1张图片

女神现在已经被别人加锁了,1号老铁已经阻塞等待一年了,2号老铁阻塞等待一个月了,
而3号老铁从昨天才开始阻塞等待的。

公平锁 就是当女神分手的时候,最先开始追女神的1号老铁先和女神谈恋爱,
后续老铁等女神再次分手才可以和女神谈恋爱。

javaEE 初阶 — 常见的锁策略_第2张图片

非公平锁 就是 “雨露均沾” 的意思。

javaEE 初阶 — 常见的锁策略_第3张图片

当女神分手的时候,三个老铁一起上,公平竞争。
最早开始追女神的1号老铁所付出的时间和精力都比其他两个老铁要多,这是就不那么公平了。

就好比某个企业有一个晋升的名额,此时应该采取的是 “先来后到” 的方式才公平。
因为先来的员工工作时间长,对公司的贡献比较大,而不应该 “雨露均沾” 的方式,
有的员工工作时间长,有的工作时间短,这样就不公平了。

操作系统和 java synchronized 元素都是 “非公平锁”
操作系统这里的针对加锁的控制,本身就依赖于线程调度顺序的。
这个调度顺序是随机的,不会考虑到这个线程等待锁多久了。

要想实现公平锁,就得在这个基础上,能够引入额外的东西。
(引入一个队列,让这些加锁的线程去排队)

但是引入额外的队列又有额外的开销和代码上成本。

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


可重入锁 :一个线程针对一把锁,连续多次加锁都不会死锁。

不可重入锁 :一个线程针对一把锁,连续两次加锁会出现死锁。

7. synchronized 是哪种锁


1、synchronized 既是一个 悲观锁 又是一个 乐观锁

synchronized 默认是乐观锁,但是如果发现当前的锁竞争比较激烈,就会变成悲观锁。


2、synchronized 即是 轻量级锁 又是 重量级锁

synchronized 默认是轻量级锁,但是如果发现当前的锁竞争比较激烈,就会变成重量级锁。


3、synchronized 这里的轻量级锁,是基于 自旋锁 的方式实现的。synchronized 这里的重量级锁,是基于挂起等待锁的方式实现的。


4、synchronized 不是 读写锁


5、synchronized 是 非公平锁


6、synchronized 是 可重入锁

8. 相关面试题


1、你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?

悲观锁 认为多个线程访问同一个共享变量冲突的概率较大,会在每次访问共享变量之前都去真正加锁。

乐观锁 认为多个线程访问同一个共享变量冲突的概率不大。并不会真的加锁, 而是直接尝试访问数
据。在访问的同时识别当前的数据是否出现访问冲突。

悲观锁 的实现就是先加锁(比如借助操作系统提供的 mutex),获取到锁再操作数据。
获取不到锁就等待。

乐观锁 的实现可以引入一个版本号,借助版本号识别出当前的数据访问是否冲突。


2、介绍下读写锁?

读写锁就是把读操作和写操作分别进行加锁.

  • 读锁和读锁之间不互斥
  • 写锁和写锁之间互斥
  • 写锁和读锁之间互斥

读写锁最主要用在 “频繁读,不频繁写” 的场景中


3、什么是自旋锁,为什么要使用自旋锁策略呢,缺点是什么?

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

相比于挂起等待锁:

优点:

没有放弃 CPU 资源,一旦锁被释放就能第一时间获取到锁,更高效,在锁持有时间比较短的场
景下非常有用。

缺点:如果锁的持有时间较长, 就会浪费 CPU 资源


4、synchronized 是可重入锁么?

是可重入锁。

可重入锁指的就是连续两次加锁不会导致死锁。

实现的方式是在锁中记录该锁持有的线程身份,以及一个计数器(记录加锁次数)。
如果发现当前加锁的线程就是持有锁的线程,则直接计数自增。

你可能感兴趣的:(java,EE,从入门到进阶,java-ee,多线程)