一. 锁的状态
1.1 前言介绍
锁的状态总共有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁。
在 JDK 1.6之前,synchronized 还是一个重量级锁,是一个效率比较低下的锁,但是在JDK 1.6后,Jvm为了提高锁的获取与释放效率对(synchronized )进行了优化,引入了 偏向锁 和 轻量级锁 ,从此以后锁的状态就有了四种(无锁、偏向锁、轻量级锁、重量级锁)。
并且四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,也就是说只能进行锁升级(从低级别到高级别),不能锁降级(高级别到低级别),意味着偏向锁升级成轻量级锁后不能降级成偏向锁。
这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。
1.2 四种状态
在 synchronized 最初的实现方式是 “阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”。
这种方式就是 synchronized实现同步最初的方式,这也是当初开发者诟病的地方,这也是在JDK6以前 synchronized效率低下的原因。
所以目前锁状态一种有四种,从级别由低到高依次是:无锁、偏向锁,轻量级锁,重量级锁,锁状态只能升级,不能降级。
二.锁的特点及对比
2.1 锁特点
锁状态 | 存储内容 | 标志位 |
无锁 | 对象的hashCode、对象分代年龄、是否是偏向锁(0) | 01 |
偏向锁 | 偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1) | 01 |
轻量级锁 | 指向栈中锁记录的指针 | 00 |
重量级锁 | 指向互斥量的指针 | 11 |
为什么无锁/偏向锁的标志位是01,而轻量级锁的标志位是00?
即按理说,无锁是锁状态的初始情况,为什么标志位不是从00开始?查询到的一个解释,是因为 轻量级锁除了锁标志位外,另外62位都是一个指针地址。
如果将轻量级锁标志位设置为00, 那么在判断标志位为00后, m无需再额外做一次markWord>>2的操作,而是直接将markWord拿来当作地址使用即可!
2.2 锁对比
锁 | 优点 | 缺点 | 适用场景 |
偏向锁 | 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距 | 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 | 适用于只有一个线程访问同步块场景 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 如果始终得不到索竞争的线程,使用自旋会消耗CPU | 追求响应速度,同步块执行速度非常快 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU | 线程阻塞,响应时间缓慢 | 追求吞吐量,同步块执行速度较慢 |
三.锁的升级过程
3.1 四种锁介绍
无锁:无锁是指没有对资源的锁定,所有线程都能访问并修改同一资源,但同时只有一个线程修改成功。
偏向锁:初次执行到synchronized 代码块的时候,锁对象变成偏向锁(通过CAS修改对象头中的标志位),字面意思是“偏向于第一个获得它的锁”,执行完同步代码块,线程并不会自动释放偏向锁,当同一个线程第二次访问的时候,就会判断对象头中的线程是否为自己(获得锁之后,会在Mark Word 里存储当前线程的ID),不需要重新加锁,没有额外开销,性能高。
那什么时候释放锁呢? 当有其他线程尝试竞争偏向锁的时候,持有偏向锁的线程才会释放锁,注意线程是不会主动释放锁的。关于偏向锁的撤销,需要等到全局安全点,即在某个时间点上没有字节码正在执行时,它会暂停拥有偏向锁的线程,然后判断锁对象是否处于锁定状态。如果不处于活动状态则撤销,恢复到无锁或者轻量级锁状态。
轻量级锁:轻量级锁是指锁是偏向级锁的时候,却被另外的线程锁访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。
获取的主要两种方式:
- 当关闭偏向锁时
- 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁
重量级锁:当线程锁竞争情况严重,某个达到最大自旋次数的线程(默认为10次),会将轻量级锁升级为重量级锁(通过CAS修改所标志位,但不修改持有ID)。当后序的线程尝试获取锁时,就将自己挂起,等待被唤醒。重量级锁将控制权交给了操作系统,有操作系统来负责线程间的调度和状态变换,会出现频繁的对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资源。
3.2 升级过程
![](https://i-blog.csdnimg.cn/blog_migrate/65bebcb59a735badbc996533548e95f6.png)
检查Mark Word 里面的线程ID,如果是1,表示当前线程处于偏向锁
如果不是,将使用CAS将当前线程的ID替换Mark Word,如果成功,则表示当前线程获取偏向锁,置偏向锁的标志位为1
如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,则当前线程获得锁
如果失败,则通过自旋来尝试继续获得锁。
如果自旋成功,而仍然处于轻量级锁。
自旋失败,锁升级为重量级锁。
以上图片 转载百度图片
总结
在有线程访问同步块的时候,通过检查对象头中 Mark Word 中存储的线程ID, 判断是否是偏向锁(更加倾向于一个线程对同步块的作用)。在出现锁竞争的时候,偏向锁升级为轻量级锁,在此状态下线程依然通过CAS 自旋的方式去获取锁,当自旋超过10次过后,锁升级为重量级锁。
关键理解对象的内存布局,和锁升级改变方式和对内存布局的影响