深入理解Java并发之Synchronized原理及锁膨胀

在座的各位都知道Synchronized是锁同步用的,但是内部原理及JDK1.6之后的优化及锁优化后的锁膨胀流程清楚么。本篇将尽量用易理解的语言梳理整个流程(附流程图)

0x00 题外话

Synchronized可以用来修饰类大家都知道,作用的对象是这个类的所有对象;

1
2
3
4
5
6
7
8
//这个类的所有对象
synchronized(Test.class){
//do something
}
//当前对象当
synchronized(this){
//do something
}

this锁当前对象可以理解,可是Test.class一般我们理解的都是一个class文件,如何做到锁住所有实例的呢?

这就要从Class类来说起了,Class类的对象作用是运行时提供或获取某个对象的类型信息,它也会在堆内存中生成一个class对象(每个class的描述信息类只会生成一个对象),只不过这个对象是描述这个对象的对象;所以只要锁住这个class文件,就会作用到这个对象的所有实例!

0x01 Synchronized底层语义原理

以前的理解:Synchronized是一个重量级锁,相对于Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它。

jdk1.6之前:所有的锁都是重量级锁,原理是调用操作系统层面的互斥锁(C++的mutex)
jdk1.6之后:引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

当java代码中使用Synchronized后,反编译可以看到两个特殊的指令:

  • monitorenter
  • monitorexit

sync-javap.png

而标记一个对象是否被锁住了,是存在JAVA对象头中。

1
2
3
4
5
java对象的组成{
对象头 12字节 (对象是哪个classGC年龄和状态、同步状态、表示哈希码)
对象实例数据
对齐字节(64位虚拟机,对象大小必须是8的整数倍, 若值10byte--分配-->16)值=对象头+实例数据
}

JAVA1.6之后优化的偏向所就是用对象头中标记的来实现的。
偏向锁轻量级锁重量级锁自旋锁锁消除

0x02 锁膨胀的过程

头标记 升级过程
无锁 001
偏向锁 101 只有单个线程执行,偏向锁延迟4s(启动等待其他代码)
轻量级锁 线程地址+00 多个线程【顺序】执行它想加锁
重量级锁 线程地址+10 多个线程竞争

自旋锁:在轻量级锁中尝试获取锁,当超过默认的阀值10就会升级成重量级锁(jdk6之后引入了自适应自旋锁,根据当前各种软硬件信息判断动态的修改阀值)
锁消除:JIT编译的时候根据逃逸分析检测到不必要的锁取消

锁膨胀流程图(新标签中打开图片):

sync-javap.png

上图来自文章Java Synchronised机制