文章目录
一、Synchronized原理介绍
1、锁对象
- 普通同步方法:锁是当前实例对象。
- 静态同步方法:锁是当前类的class对象。
- 同步方法:锁是括号里面的对象。
2、实现机制
- Java对象头
- monitor
3、锁优化
- 轻量级锁
- 重量级锁
- 锁消除
- 锁粗化
- 偏向锁
4、使用方式
- 普通同步方法:锁是当前实例对象。
- 静态同步方法:锁是当前类的class对象。
- 同步方法:锁是括号里面的对象。
二、Synchronized锁升级
1、jdk8 markword实现表
为什么有自旋锁还需要重量级锁:
- 自旋消耗CPU资源,如果锁的时间长,或者自旋线程多,CPU会被大量消耗。
- 重量级锁有等待队列,所有拿不到锁的进入等待队列,不需要消耗CPU资源。
偏向锁是否一定比自旋锁效率高:
- 不一定,在明确知道会有多线程竞争的情况下,偏向锁肯定会涉及锁撤销,这时候直接使用自旋锁。
- JVM启动过程,会有多个线程竞争,所以默认情况启动时不打开偏向锁,过一段时间再打开。
new -> 偏向锁 -> 轻量级锁(自旋锁、自适应自旋锁)-> 重量级锁:
- 偏向锁和轻量级锁都是用户空间完成的。
- 重量级锁是需要向内核申请的。
synchronized优化的过程和markword息息相关,markword中最低的三位代表锁状态,其中1位是偏向锁位,两位是普通锁位。
2、使用工具来查看锁升级
引入依赖:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
由上图分析可知刚new出来的对象处于无锁状态,当使用synchronized关键字后处于轻量级锁状态。为什么锁的状态标志在高位,这个和计算机的大小端有关,可参考:计算机中的大小端
3、默认synchronized(o)
00 -> 轻量级锁,默认情况下偏向锁有个时延,默认是4秒。因为JVM虚拟机自己有一些默认启动的线程,里面有好多sync代码,这些syanc代码启动时就知道肯定会有竞争,如果使用偏向锁,就会造成偏向锁不断的进行锁升级的操作,效率低。
-XX:BiasedLockingStartupDelay=0
由上图可知new出来的对象是匿名偏向锁,synchronized加锁后变成了偏向锁。
- 如果设定上述参数
new Object() -> 101偏向锁 -> 线程ID为0 -> Anonymous BiasedLock。打开偏向锁,new出来的对象,默认就是一个可偏向匿名对象101。 - 如果有线程上锁
上偏向锁:指的就是把markword的线程ID改为自己线程ID的过程,偏向锁不可重偏向,批量偏向,批量撤销(也就是锁没有线程竞争)。 - 如果有线程竞争
撤销偏向锁,升级轻量级锁。线程在自己的线程栈生成LockRecord,用CAS操作将markword设置为指向自己这个线程的LR的指针,设置成功者得到锁。 - 如果竞争加剧
竞争加剧:有线程超过10次自旋,-XX:PreBlockSpin,或者自选线程数超过CPU核数一半。1.6之后,加入自适应自旋Adapative Self Spinning,JVM自己控制。
升级重量级锁:-> 向操作系统申请资源,linux mutex,CPU从3级-0级系统调用,线程挂起,进入等待队列,等待操作系统的调度,然后再映射回用户空间。 - 锁的优缺点对比