偏向锁的字面意思就是"偏向于第一个获取它的线程"的锁 。线程执行完同步代码块之后,并不会主动释放偏向锁 。当第二次到达同步
代码块时,线程会判断此时持有锁的线程是否就是自己(持有锁的线程 ID 在对象头里存储),如果是则正常往下执行 。由于之前没有释放,
这里就不需要重新加锁,如果从头到尾都是一个线程在使用锁,很明显偏向锁几乎没有额外开销,性能极高 。

文章插图
一旦有第二个线程加入锁竞争,偏向锁转换为轻量级锁(自旋锁) 。锁竞争:如果多个线程轮流获取一个锁,但是每次获取的时候
都很顺利,没有发生阻塞,那么就不存在锁竞争 。只有当某线程获取锁的时候,发现锁已经被占用,需要等待其释放,则说明发生了锁竞争 。
在轻量级锁状态上继续锁竞争,没有抢到锁的线程进行自旋操作,即在一个循环中不停判断是否可以获取锁 。获取锁的操作,就是通过 CAS 操
作修改对象头里的锁标志位 。先比较当前锁标志位是否为释放状态,如果是,将其设置为锁定状态,比较并设置是原子性操作,这个
是 JVM 层面保证的 。当前线程就算持有了锁,然后线程将当前锁的持有者信息改为自己 。
假如我们获取到锁的线程操作时间很长,比如会进行复杂的计算,数据量很大的网络传输等;那么其它等待锁的线程就会进入长时间的自旋操作,这个
过程是非常耗资源的 。其实这时候相当于只有一个线程在有效地工作,其它的线程什么都干不了,在白白地消耗 CPU,这种现象叫做忙等
(busy-waiting) 。所以如果多个线程使用独占锁,但是没有发生锁竞争,或者发生了很轻微的锁竞争,那么 synchronized 就是轻量
级锁,允许短时间的忙等现象 。这是一种择中的想法,短时间的忙等,换取线程在用户态和内核态之间切换的开销 。

文章插图
显然,忙等是有限度的(JVM 有一个计数器记录自旋次数,默认允许循环 10 次,可以通过虚拟机参数更改) 。如果锁竞争情况严重,
达到某个最大自旋次数的线程,会将轻量级锁升级为重量级锁(依然是通过 CAS 修改锁标志位,但不修改持有锁的线程 ID) 。当后续线程尝试获取
锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是上面说的忙等,即不会自旋),等待释放锁的线程去唤醒 。在 JDK1.6 之前,synchronized
直接加重量级锁,很明显现在通过一系列的优化过后,性能明显得到了提升 。
JVM 中,synchronized 锁只能按照偏向锁、轻量级锁、重量级锁的顺序逐渐升级(也有把这个称为锁膨胀的过程),不允许降级 。
可重入锁(递归锁)可重入锁的字面意思是"可以重新进入的锁",即允许同一个线程多次获取同一把锁 。比如一个递归函数里有加锁操作,递归函数里这个锁会阻塞自己么?
如果不会,那么这个锁就叫可重入锁(因为这个原因可重入锁也叫做递归锁) 。
Java 中以 Reentrant 开头命名的锁都是可重入锁,而且 JDK 提供的所有现成 Lock 的实现类,包括 synchronized 关键字锁都是可重入的 。
如果真的需要不可重入锁,那么就需要自己去实现了,获取去网上搜索一下,有很多,自己实现起来也很简单 。
如果不是可重入锁,在递归函数中就会造成死锁,所以 Java 中的锁基本都是可重入锁,不可重入锁的意义不是很大,我暂时没有想到什么场景下会用到;
注意:有想到需要不可重入锁场景的小伙伴们可以留言一起探讨 。
下图展示一下 Lock 的相关实现类:

文章插图
ilock.png
公平锁和非公平锁
如果多个线程申请一把公平锁,那么获得锁的线程释放锁的时候,先申请的先得到,很公平 。如果是非公平锁,后申请的线程可能先获得锁,是
随机获取还是其它方式,都是根据实现算法而定的 。
对 ReentrantLock 类来说,通过构造函数可以指定该锁是否是公平锁,默认是非公平锁 。因为在大多数情况下,非公平锁的吞吐量比公平锁的大,
如果没有特殊要求,优先考虑使用非公平锁 。
而对于 synchronized 锁而言,它只能是一种非公平锁,没有任何方式使其变成公平锁 。这也是 ReentrantLock 相对于 synchronized 锁的一个
推荐阅读
- 为什么关羽死后吕蒙也死了 历史上关羽是不是被吕蒙所杀
- 马皇后结局怎么样 历史上真实的马皇后
- Java异常处理只有Try-Catch吗?
- JavaScript原型详解
- 教你怎么从Java8升级到Java11
- 历史上真实康熙的容妃 康熙王朝里容妃结局
- 冬春交替如何保健养生?为你整理最全饮食宝典
- 史上最大的啮齿动物 什么动物吃蛇最厉害
- 10个最受欢迎的 JavaScript 框架,以及它们的主要特征和功能
- 杨贵妃可能被误会了几千年 历史上的杨贵妃
