一、synchronized加锁原理
由于synchronized总体的工作原理是通过操作对象头来实现加锁和解锁,因此在具体的了解synchronized之前,首先需要简单了解一下对象头。
所谓对象头其实是每个对象都存在的一份存储当前对象gc分代年龄、hashcode、锁标记、当前获取到锁的线程ID等信息的一块内存区域。对象头主要分为以下三块:
1.Mark Word
2.指向类的指针
3.数组长度(只有数组对象才有)
synchronized在加锁过程中主要对mark word进行操作,因此主要对mark word的划分,各个划分快存储的内容进行探索。如下图所示mark word的划分以及各个划分块存储的内容:
在64位jvm中mark word的长度为64bit,从图中我们可以看出最后两位用来作为锁标记位,在无锁和偏向锁状态时还存在1bit的偏向锁位用来标记是否是可偏向的。
我们可以使用jol来查看对象头信息,要是有jol需要引入如下包:
org.openjdk.jol
jol-core
0.8
执行以下代码我们可以简单看一下对象头信息:
Object o=new Object();
System.out.println(VM.current().details());
System.out.println(ClassLayout.parseInstance(o).toPrintable());
执行结果:
从执行结果我们可以看到对象头总共占了96bit【空框1】,其中mark Word占了64bit【红框2】,最后三位的状态为001说明当前处于无锁状态。
从上边我们知道了synchronized在加锁解锁时操作的内存空间,那么synchronized是如何操作的,在什么时候进行操作,操作了什么内容呢,下文将进一步了解。
使用javap反编译工具对含有synchronized代码块的代码进行反汇编,执行javap -v App.class的运行结果:
可以看到在被synchronized包起来的代码块前后增加了monitorenter和monitorexit两条汇编指令,当代码执行到monitorenter指令时开始加锁操作,大概执行过程如下:
在代码进入代码块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为Lock Record的空间,用于存储锁对象目前的Mark Word的拷贝,然后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为Lock Record的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且队形Mark Word的锁标志位(Mark Word的最后两个bits)将转变为‘00’,即表示此对象处于轻量级锁定的状态。
—深入理解JVM
二、synchronized锁膨胀过程
锁膨胀是从偏向锁—>轻量级锁---->重量级锁的转换过程。在探究膨胀过程之前,我们先简单了解一下偏向锁、轻量级锁、重量级锁的概念。
①重量级锁:传统重量级锁指使用操作系统互斥量来实现加锁(PV操作),使用这种锁的话,即使没有所竞争的时候还是会调用操作系统级别的互斥量实现加锁,造成性能消耗。
②轻量级锁:通过简单的数据操作实现加锁,不需要调用OS级别的加锁机制。
*在当前线程自己的栈区创建Lock Record用于存储Object的Mark Word的拷贝
*将Mark Word副本存入lock record
*基于CAS的机制将Object的Mark Word跟新为执行Lock record的指针
*如果CAS跟新成功,则当前线程获取到锁,否则检查Mark Word出的指针是否指向自己的栈针
*如果指向自己的栈针,则说明已经获取到锁,则重入代码快执行,否则说明锁已经被占用,则膨胀为重量级锁
③偏向锁:偏向锁可以说是在轻量级锁上的进一步优化,轻量级锁在没有锁竞争的条件下还需要执行CAS操作,偏向锁连CAS都不操作。所谓“偏”就是说偏袒于第一次获得锁的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将不需要执行同步操作,直接获得运行资源。直到另外一个线程到来,偏向锁开始膨胀。
1.配置虚拟机开启偏向锁
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0