Java 6 对 synchronized 锁做了多方面的优化,其中最主要的就是引入了 偏向锁和轻量级锁。锁的获取次序依次是 偏向锁 -> 轻量级锁 -> 重量级锁。
这3种锁的膨胀过程都是在 JVM 源码中的 synchronizer.cpp 实现,具体路径:
jdk7u-hotspot/src/share/vm/runtime/synchronizer.cpp
synchronized 修饰的代码经过编译之后,会在代码前后插入 monitorenter 和 monitorexit 这两个指令。这两个指令会分别执行到源码的 InterpreterRuntime.cpp 中:
InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)
InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem)
参数解释:
JavaThread指向java中的获取锁的当前线程
BasicObjectLockBasicObjectLock类型的elem对象 内部包含一个BasicLock对象 _lock 和一个指向Object对象的指针 _obj
BasicObjectLock 结构如下图:
我们从 monitorenter 为入口,沿着偏向锁 -> 轻量级锁 -> 重量级锁的膨胀过程来分析 synchronized 的实现过程。
monitorenter
源码如下:
图中标识 UseBiasedLocking 是判断JVM是否启动了偏向锁开关,如果打开则调用 fast_enter 获取 偏向锁 ;否则调用 slow_enter 获取 轻量级锁 。
偏向锁
获取偏向锁的操作是在 fast_enter 函数中实现,具体逻辑如下:
fast_enter 流程说明:
再次检查偏向锁是否开启
当处于不安全点时, 调用 BiasedLocking的 revoke_and_rebias 函数来尝试获取偏向锁 ,如果成功则直接返回;如果失败则进入轻量级锁获取过程。这个函数具体实现是在 biasedLocking.cpp 文件中
如果偏向锁未开启,则进入 slow_enter获取轻量级锁的流程
revoke_and_rebias
部分实现逻辑如下:
解释说明:
通过markOop mark = obj -> mark() 获取对象的 markOop 数据mark,即对象头中的Mark Word
判断mark -> has_bias_pattern 判断所对象是否为可偏向状态,也就是 Mark Word 的偏向锁标志位为 1,锁标志位为 01
判断 Mark Word 中线程ID:如果为空,则进入步骤(4);如果指向当前线程,说明是重入锁,直接执行同步代码块即可;如果指向其它线程,说明当前存在多个线程竞争锁,需要将其升级为轻量级锁
通过CAS原子指令设置Mark Word 中线程ID为当前线程ID,如果执行CAS成功,则执行同步代码块;否则说明存在多线程竞争锁,需要撤销偏向锁并升级为轻量级锁
通过工具JOL(Java Object Layout)可以查看Java对象在内存中的具体情况。比如以下代码:
调用 ClassLayout.parseInstance(testDemo) 方法, 获取锁对象testDemo的内存情况,执行上述代码打印结果如下:
图中header的前8个字节按照平时习惯的从高位到低位的展示为:
00000000 00000000 00000000 00111001 10101110 11101101 00101111 00000 101
后3位分别代表是否偏向锁和锁标志位,分析图中结果如下:
加锁前: 101 虽然是 偏向锁 ,没有任何线程持有锁(也就是Mark Word中的线程ID为空)
加锁中: 101 表示 偏向锁 ,当前main线程拿到锁(也就是Mark Word中的线程ID指向main线程)
加锁后: 101 表示 偏向锁 ,表示当前锁对象依然是偏向锁状态(偏向锁退出锁后依然是偏向状态)
注意 :在上述代码开始阶段,使用 sleep(5000) 睡眠5秒钟,是因为虚拟机在启动时会有4秒钟的偏向锁延迟,源码如下所示:
路径:openjdk/hotspot/src/share/vm/runtime/globals.hpp
BiasedLockingStartupDelay, 4000 //偏向锁延迟4000ms
product(intx, BiasedLockingStartupDelay, 4000,
"Number of milliseconds to wait before enabling biased locking")
range(0, (intx)(max_jint-(max_jint%PeriodicTask::interval_gran)))
constraint(BiasedLockingStartupDelayFunc,AfterErgo)
轻量级锁
如果说偏向锁是只允许一个线程获得锁,那么轻量级锁就是允许多个线程获得锁, 但是只允许他们顺序拿锁,不允许出现竞争 ,也就是拿锁失败的情况。
轻量级锁的实现在 slow_enter 函数中,具体逻辑如下:
slow_enter 流程说明:
当前线程的栈帧中创建一个锁记录的空间,这个空间存储对象头中Mark World的拷贝,就是复制一份到这个锁记录空间
然后虚拟机使用CAS尝试将这个锁记录空间的指针更新到Mark World,如果CAS操作成功,那么当前线程获取到锁,此时锁状态处于轻量级锁,锁标志位置为00
如果CAS失败,则调用 ObjectSynchronizer::inflate 膨胀为重量级锁
获取轻量级锁案例
如下代码:
分别打印出默认情况(main线程加锁)下和线程 t1 加锁后锁对象testDemo的状态,结果如下:
可以看出当有其它线程竞争锁对象时,锁对象 Mark Word 中的锁标志为被位置 00。
注意:重量级锁是通过对象内部的监视器(monitor)来实现,而monitor的本质是依赖操作系统底层的MutexLock实现的,这一节不做详细介绍,后续有时间再考虑重写一篇专门分析synchronized重量级锁的实现源码分析。