【Java】锁策略

锁策略

      • 乐观锁 和 悲观锁
      • 轻量级锁 和 重量级锁
      • 自旋锁 和 挂起等待锁
      • 普通互斥锁 和 读写锁
      • 公平锁 和 非公平锁
      • 可重入锁 和 不可重入锁

乐观锁 和 悲观锁

这是两种不同的锁的实现方式

  1. 乐观锁:在加锁之前,预估当前出现锁冲突的概率不大,因此在进行加锁的时候就不会做太多的工作
    • 加锁过程做的事情比较少,加锁的速度更快,但是更容易引入一些其他的问题(消耗更多的CPU资源)
  2. 悲观锁:在加锁之前,预估当前出现锁冲突的概率较大,因此在进行加锁的时候,就会做更多的工作
    • 加锁过程做的事情更多,加锁的速度可能更慢,但是整个过程不容易出现其他问题

synchronized 初始使用乐观锁策略。当发现锁竞争比较频繁时,就会自动切换成悲观锁


轻量级锁 和 重量级锁

  1. 轻量级锁:加锁的开销小,加锁的速度更快。我们认为轻量级锁就是 -> 乐观锁
  2. 重量级锁:加锁的开销大,加锁的速度更慢。我们认为重量级锁就是 -> 悲观锁

轻量、重量,是对加锁之后,结果的评价
乐观、悲观,是对加锁之前,未发生的事情进行的预估
整体来说,这只是同一件事,两种不同的角度去看待


自旋锁 和 挂起等待锁

  1. 自旋锁:就是轻量级锁的一种典型实现

    • 进行加锁时,搭配一个 while 循环。如果加锁成功,循环结束;若不成功,线程不是直接阻塞放弃去利用CPU,而是继续进行下一次循环,再次尝试获取到锁。这个反复快速获取锁的过程,就称为 “自旋“。这也是属于乐观锁。

    也就是说 “自旋锁不会被 CPU 调度走,不会有调度开销,因此 ”自旋锁“ 的效率比 ”挂起等待锁“ 的效率高,但是 ”自旋锁会不断查看 锁状态,会浪费更多的CPU资源

  2. 挂起等待锁:就是重量级锁的一种典型实现

    • 这个就和自旋锁相反,一旦获取锁失败,那么就放弃利用CPU资源,进入 ”挂起等待“ 状态,一旦 ”听说“ 释放锁了,执行挂起等待锁的线程也会再一次去尝试获取锁
    • 挂起等待锁,同时也是个悲观锁,适用于锁冲突激烈的情况

普通互斥锁 和 读写锁

  • 普通互斥锁

    类似于 synchronized 操作,涉及到 加锁 和 解锁

  • 读写锁

    把加锁分成了两种情况:

    1. 加 “” 锁
    2. 加 “” 锁

首先是:多个线程对 读写锁 的申请:
【Java】锁策略_第1张图片

  • 总结:
    1. “读” 数据是不涉及数据修改的,就不会发生线程安全问题,所以多个线程 “读” 是被允许的。
    2. 但 “写” 数据是不允许其他线程来 “读” 或 “写”,也就是说只能有唯一一个线程能够持有 “写” 锁,只能由该线程来 “写”数据。

其次是:单个线程对 读写锁 的持有情况:
文章引用:读写锁——ReentrantReadWriteLock原理详解)
这里直接说总结:

在一个线程持有 读 锁的情况下,该线程不能获取写锁。
在一个线程持有 写 锁的情况下,该线程可以继续获取读锁。

仔细想想,这个设计是合理的:因为当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了读写锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。

综上:
一个线程要想同时持有写锁读锁,必须**获取写锁获取读锁**;写锁可以“降级”为读锁读锁不能“升级”为写锁。


为什么要引入读写锁?

如果两个线程在读操作,这个本身是线程安全的,不需要进行互斥
但如果使用 synchronized 这种方式加锁,两个线程进行读操作,是会产生互斥、阻塞的,对于性能有一定的损失
完全给读操作不加锁,也不行,万一有一个线程在读,另一个线程在写,那么读到的就可能是一个 “脏” 的数据

读写锁,就可以很好地解决上述问题


公平锁 和 非公平锁

此处定义的 “公平”,要遵守 “先来后到“ 的规则,才叫公平

  1. 公平锁:B 比 C 先来的。当 A 释放锁之后,B 就能先于 C 获取到锁
  2. 非公平锁:不遵守 “先来后到”。B 和 C 都有可能获取到锁

注意:

  • 操作系统内部的线程调度就可以视为是随机的。如果不做任何额外的限制,锁就是非公平锁。如果要想实现公平锁,就要依赖 额外的数据结构,来记录线程们的先后顺序
  • 公平锁和非公平锁之间没有好坏之分,关键还是看适用场景
  • synchronized 是非公平锁
  • 使用公平锁,天然就可以避免 “线程饿死” 的问题

可重入锁 和 不可重入锁

  • 可重入锁:一个线程针对一把锁,连续加锁多次,不会死锁
  • 不可重入锁:一个线程针对一把锁,连续加锁两次就会造成死锁

可重入锁,需要记录持有锁的线程是谁,以及加锁的次数

你可能感兴趣的:(java,开发语言)