目录
为保证线程安全,我们常常进行加锁操作,那在 Java 中都有哪些 锁策略呢?
1. 乐观锁 VS 悲观锁
乐观悲观与否,取决于后续锁冲突是否激烈(频繁)给出的预测。
(1)乐观锁:预测 后续 锁冲突较小,就可以少做一些工作。
(2)悲观锁: 预测 后续 锁冲突较大,就需要多做一些工作。
2. 轻量级锁 VS 重量级锁
重量,轻量 是看 锁开销大小来决定的。
(1)轻量级锁:锁开销相对较小。 通常也就是轻量级锁(不一定)
典型的是纯用户态的加锁解锁逻辑,开销是比较少的。
(2) 重量级锁: 锁开销相对较大。 通常也就是重量级锁(不一定)
典型的是进入了系统内核态的加锁解锁逻辑,开销是比较大的。
3. 自旋锁 VS 挂起等待锁
(1)自旋锁:属于轻量级锁的一种典型的实现。
自旋锁会反复 获取 锁的状态,属于”忙等(一直等,啥事不干)“,直到获取到锁。
虽然这消耗了 cpu 的资源,但是 换来了 更快的响应速度。
(2) 挂起等待锁: 属于重量级锁的一种典型实现。
挂起等待锁 需要借助 系统api 来实现,当出现锁竞争的时候,线程就会阻塞,并且该线程就不会参与 cpu 的调度了,节省了cpu 资源。
4. 读写锁
这里的读写锁 与 数据库中 ” 事务“ 中的 给读加锁 和 给写加锁 不一样。
读写锁 , 是将 加锁操作,分为了 读锁 以及 写锁 。
当我们两个线程之间进行加锁过程中:
1. 读锁 与 读锁 之间 : 不会产生 竞争 。
2. 读锁 与 写锁 之间 :有竞争。
3. 写锁 与 写锁 之间 :有竞争。
由于我们 实际的开发的过程中,大多是 ” 读多,写少“的情况,此时读写锁就排上用场。
5. 可重入锁 VS 不可重入锁
重入与否 ,取决于 当一个线程针对一把锁 进行多次加锁,是否会出现 ”死锁“ 的情况
(1)可重入锁:针对上述情况,不会出现 “死锁” 问题。
可重入锁为什么不会出现 ”死锁“ 的情况呢:
这是因为可重入锁中引入了 计数器,首先它会先记录是谁获取的这把锁,如果是相同的线程再继续获取,它就会把计数器进行+1.
(2) 不可重入锁: 连续加锁多次就会出现 ”死锁“ 问题。
不可重入锁就没有 可重入锁的机制,所以就会出现死锁。
6. 公平锁 VS 非公平锁
当很多线程 尝试去对一把锁进行加锁操作时,一个线程拿到锁,其他线程就只能阻塞等待,当这个线程释放锁之后,接下来是哪个线程获取锁呢?
公平,非公平就是针对这个问题来定义的。
(1) 公平锁: 按照 ”先来后到“ 的顺序进行 获取锁的操作。
(2)非公平锁: 线程以 ”均等“ 的概率获取锁。(操作系统中提供的 加锁 api 就属于这种)
7. 关于 synchronized 的锁策略
(1)既是乐观锁,也是悲观锁
(2)既是轻量级锁,也是重量级锁
(3)既是自旋锁,也是挂起等待锁
(4)不是读写锁,是普通互斥锁
(5)是非公平锁
(6)是可重入锁
synchronized 是自适应的:在初始情况下,synchronized 会先预测当前 锁冲突 的概率,如果概率不大,就会以乐观锁的模式来运行(轻量级锁,基于自旋锁的方式实现);后续,当锁冲突变大时,它就会升级为 重量级锁(悲观锁,基于挂起等待锁的方式实现)。