Synchronize原理

1.对象存储布局


对象头:Java对象头一般占有2个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit,在64位虚拟机中,1个机器码是8个字节,也就是64bit)。如果对象是数组类型,则需要3个机器码,用第三个机器码来记录数组长度。
实例数据:存放类的属性数据信息,包括父类的属性信息。
对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。


2.对象头

对象头结构

对象头由三部分组成:Mark Word(标记字段),Class Pointer(类型指针)和数组长度,三个部分分别占用一个机器码大小。
Mark Word:用于存储对象自身的运行时数据,包括哈希码(HashCode),GC分代年龄,锁状态标志,线程持有的锁,偏向线程 ID,偏向时间戳等。
Class Pointer:对象指向它的class类元数据的指针,能够通过这个指针来确定对象是哪个类的实例。
Array length:存储数组的长度,只有数组对象才有该字段。

3.Mark Word(标记字段)

32位JVM无锁状态


32位JVM有锁状态

Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间。即Mark Word会随着程序的运行发生变化,可能变化为存储以上四种数据。注意不管数据结构怎么变,最后的两个bit都是锁标志位。

4.锁的状态

锁主要存在四种状态:无锁、偏向锁、轻量级锁、重量级锁。锁可以从偏向锁升级到轻量级锁,再升级的重量级锁。但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。

偏向锁:在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程进入和退出同步块时不需要花费CAS操作来争夺锁资源,只需要检查是否为偏向锁、锁标识为以及ThreadID即可。

轻量级锁:依据是 “对于绝大部分的锁,在整个生命周期内都是不会存在竞争的”。

重量级锁:通过对象内部的一个叫做 监视器锁(Monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为 “重量级锁”。

对比总结:如果是单线程使用,那偏向锁毫无疑问代价最小,并且它就能解决问题,连CAS都不用做,仅仅在内存中比较下对象头就可以了。如果出现了其他线程竞争,则偏向锁就会升级为轻量级锁。如果其他线程通过一定次数的CAS尝试没有成功,则进入重量级锁。假如遇到锁升级,同步代码块就要做偏向锁建立、偏向锁撤销、轻量级锁建立、升级到重量级锁,最终还是得靠重量级锁来解决问题,那这样的代价就比直接用重量级锁要大不少了。

锁对比


5.Synchronized的用法

Synchronized可以把任何一个非null对象作为"锁",有个专门的名字叫对象监视器(Object Monitor)。总共有三种用法:
1.当synchronized作用在实例方法时,监视器锁(monitor)便是对象实例(this)。
2.当synchronized作用在静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁。
3.当synchronized作用在某一个对象实例时,监视器锁(monitor)便是括号括起来的对象实例。


6.synchronized的实现

Synchronized关键字反编译后,主要是在同步代码块的前后分别调用了monitorEnter 和monitorExit两个方法。即进入同步代码块之前会调用monitorEnter 指令,退出同步代码块会执行monitorExit指令。发生异常退出也会执行monitorExit指令,JVM会保证这两个指令成对出现。
JVM解释器执行monitorEnter指令时,实际上时进入native方法调用,这里会根据对象头的状态,使用偏向锁,轻量级锁或者重量级锁去竞争共享对象,同时在此处也会根据竞争的情况把锁进行膨胀升级。


7.JVM对锁的优化

锁消除和锁粗化

锁消除:JVM检测到不可能存在共享数据竞争,此时JVM会对这些同步锁进行锁消除。append操作是加锁的,但buffer变量没有逃逸,不存在多线程共享,因此可以直接消除append操作的锁。
锁粗化:JVM检测到对同一个对象(buffer)连续加锁、解锁操作,会合并一个更大范围的加锁、解锁操作,即加锁解锁操作会移到for循环之外。
锁升级:Synchronized关键字在JDK1.5之前只使用重量级锁,在JDK1.6之后引入了偏向锁和轻量级锁。会根据对象头的状态,选择使用偏向锁,轻量级锁和重量级锁。同时也会动态的升级锁(即膨胀,把对象头的锁逐步升级,偏向锁->轻量级锁->重量级锁)。

你可能感兴趣的:(Synchronize原理)