轻量级锁与偏向锁

轻量级锁与偏向锁

要了解轻量级锁与偏向锁的原理和运作过程,需要先了解Hotspot虚拟机的对象头部分的内存布局。

1. 对象头

  • 对象自身的运行时数据
    如:哈希吗(HashCode)、GC分代年龄(Generational GC Age)等,这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit,简称“Mark Word”
  • 如果对象是数组类型,则虚拟机用3个Word(字宽)存储对象头,如果对象是非数组类型,则用2Word存储对象头。
  • 指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。
  • 对象头信息是与对象自身定义的数据无关的额外存储成本。它会根据对象的状态复用自己的存储空间。例如:在32位的HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32bit空间中的25bit用于存储对象哈希吗(HashCode),4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0

其他状态下对象的存储内容

锁状态包括:轻量级锁定、重量级锁定、GC标记、可偏向

32位JVM的Mark Word的默认存储结构如下:

64位JVM下, Mark Word是64bit大小的,存储结构如下:
轻量级锁与偏向锁_第1张图片


2. 偏向锁

JDK1.6引入

  • 优点:消除数据在无竞争情况下的同步原语,提高性能。
  • 偏向锁与轻量级锁理念上的区别:
    • 轻量级锁:在无竞争的情况下使用CAS操作去消除同步使用的互斥量
    • 偏向锁:在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了
  • 意义:锁偏向于第一个获得它的线程。如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。
  • 相关参数:
    • 默认-XX:+UseBiasedLocking=true
    • -XX:-UseBiasedLocking=false关闭偏向锁
    • 应用程序启动几秒钟之后才激活
    • -XX:BiasedLockingStartupDelay = 0关闭延迟

2.1 加锁

  • 当锁对象第一次被线程获取的时候,虚拟机把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中的偏向线程ID,并将是否偏向锁的状态位置置为1。

    • 如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,直接检查ThreadId是否和自身线程Id一致,
      • 如果一致,则认为当前线程已经获取了锁,虚拟机就可以不再进行任何同步操作(例如Locking、Unlocking及对Mark Word的Update等)。
  • 当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就如上面介绍的轻量级那样执行。

偏向锁、轻量级锁的状态转化及对象Mark Word的关系如下图:
轻量级锁与偏向锁_第2张图片

2.2偏向锁的获取和撤销流程

挂起拥有偏向锁的线程后,存在竞争则将Mark Word恢复为轻量级锁,并将该锁赋予当前堆栈中最近的一个lock record。

2.3 总结

偏向锁可以提高带有同步但无竞争的程序性能。如果程序中大多数的锁总是被多个不同的线程访问,那偏向模式就是多余的。在具体情形分析下,禁止偏向锁优反而可能提升性能。

3. 轻量级锁

3.1 加锁

  • 在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁记录目前的Mark Word的拷贝(称为Displaced Mark Word)
    • 拷贝mark word的作用:为了不想在lock与unlock这种底层操作上再加同步。
    • 修改Object mark word轻量级锁指针作用:告诉其他线程,该object monitor已被占用
    • owner指向object mark word作用:在下面的运行过程中,识别哪个对象被锁住了。

这时候堆栈与对象头的状态如下

  • 然后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后2bit)将转变为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如下

  • 如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧。如果指向,说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为”10”,Mark Word中存储的就是指向重量级(互斥量)的指针。

3.2 解锁

与加锁一样通过CAS操作进行的,如果对象的Mark Word仍然指向着线程的锁记录,那就用CAS操作把对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。

3.3 lock,unlock与mark word之间的联系

JDK1.5以后,不存在OS原语同步了。

3.4 两个线程同时争夺锁,导致锁膨胀的流程图

3.5 总结

轻量级锁能提高程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。


参考

  1. 深入理解Java虚拟机:JVM高级特性与最佳实践
  2. http://blog.csdn.net/songylwq/article/details/5585734
  3. http://ifeve.com/java-synchronized/
  4. http://blog.163.com/silver9886@126/blog/static/35971862201472274958280/

你可能感兴趣的:(java虚拟机)