synchronized锁升级原理
简单版
在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候threadid 为空,jvm 让其持有偏向锁,并将threadid设置为其线程id,再次进入的时候会先判断threadid是否与其线程id一致。
如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
详细版
-
对象没有被当做锁的时候,为普通对象,Mark Word锁标志位为01,偏向锁位为0,呈现无锁状态
-
线程A要执行同步锁的代码时,该对象被当做同步锁,线程抢到该锁时,该对象的对象头当中Mark Word字段中的锁标志位为01,偏向锁位为1,前23位记录线程A的ID,升级为偏向锁
-
线程A再次试图获得锁时,检测锁对象的Mark Word里面是不是线程A的ID
-
如果是,表示线程A获得该偏向锁,可以执行同步锁的代码
-
如果不是,则使用CAS尝试替换偏向锁Mark Word中的线程ID
-
如果成功则表示当前线程获得偏向锁
-
如果失败,则说明发生竞争,对象锁撤销偏向锁,进而升级为轻量级锁(这时对象锁的Mark Word不再保存线程ID,而是保存占有该锁线程的锁记录指针),锁标志位改为00。进入4
-
-
-
当前线程不断自旋通过CAS尝试将对象锁的Mark Word替换为自己的锁记录指针(当前线程的栈中开辟一块单独的空间,里面保存指向对象锁Mark Word的指针)
-
如果成功,当前线程获得轻量锁
-
如果失败,当前线程继续自旋来获取锁。
-
如果自旋成功,则获取轻量锁。
-
如果自旋失败(自旋超过默认的10次或者自旋等待线程超过CPU线程的二分之一),JVM向操作系统申请一个监视器对象,对象锁的Mark Word中锁标志位改为10,其他区域改为监视器指针,升级为重量锁。进入5
-
-
-
重量锁生成等待队列,未抢到锁的线程都进入等待队列
锁升级的目的
锁升级是为了减低了锁带来的性能消耗。在Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。