Java轻量级锁原理详解(Lightweight Locking)

大家知道,Java的多线程安全是基于Lock机制实现的,而Lock的性能往往不如人意。
原因是,monitorenter与monitorexit这两个控制多线程同步的bytecode原语,是JVM依赖操作系统互斥(mutex)来实现的。
互斥是一种会导致线程挂起,并在较短的时间内又需要重新调度回原线程的,较为消耗资源的操作。

为了优化Java的Lock机制,从Java6开始引入了轻量级锁的概念。

轻量级锁(Lightweight Locking)本意是为了减少多线程进入互斥的几率,并不是要替代互斥。
它利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG),尝试在进入互斥前,进行补救。

本文将详细介绍JVM如何利用CAS,实现轻量级锁。

原理详解

Java Object Model中定义,Object Header是一个2字(1 word = 4 byte)长度的存储区域。
第一个字长度的区域用来标记同步,GC以及hash code等,官方称之为 mark word。第二个字长度的区域是指向到对象的Class。

在2个word中,mark word是轻量级锁实现的关键。它的结构见下表

从表中可以看到,state为lightweight locked的那行即为轻量级锁标记。bitfieds名为指向lock record的指针,这里的lock record,其实是一块分配在线程堆栈上的空间区域
用于CAS前,拷贝object上的mark word(为什么要拷贝,请看下文)。
第三项是重量级锁标记。后面的状态单词很有趣,inflated,译为膨胀,在这里意思其实是锁已升级到OS-level。
在本文的范围内,我们只关注第二和第三项即可。

为了能直观的理解lock,unlock与mark word之间的联系,我画了一张流程图:

在图中,提到了拷贝object mark word,由于脱离了原始mark word,官方将它冠以displaced前缀,即displaced mark word(置换标记字)。
这个displaced mark word是整个轻量级锁实现的关键,在CAS中的compare就需要用它作为条件。

为什么要拷贝mark word?
其实很简单,原因是为了不想在lock与unlock这种底层操作上再加同步。

在拷贝完object mark word之后,JVM做了一步交换指针的操作,即流程中第一个橙色矩形框内容所述。
将object mark word里的轻量级锁指针指向lock record所在的stack指针,作用是让其他线程知道,该object monitor已被占用。
lock record里的owner指针指向object mark word的作用是为了在接下里的运行过程中,识别哪个对象被锁住了。

下图直观地描述了交换指针的操作。

exchange_pointer_1

最后一步unlock中,我们发现,JVM同样使用了CAS来验证object mark word在持有锁到释放锁之间,有无被其他线程访问。
如果其他线程在持有锁这段时间里,尝试获取过锁,则可能自身被挂起,而mark word的重量级锁指针也会被相应修改。
此时,unlock后就需要唤醒被挂起的线程。

转载请注明原文链接:http://kenwublog.com/theory-of-lightweight-locking-upon-cas

2009/11/08 | Posted in Concurrency
  1. xz
    2009/11/09 09:17 | #1

    恩,学习了。。很不错。

  2. Kelvin CHI
    2009/11/09 09:18 | #2

    好文….学习了…

  3. 2009/11/09 19:36 | #3

    博客不错。。支持哦,多多交流

  4. wyz
    2009/11/10 10:20 | #4

    好文, 喜欢, 留个脚印, 已订阅到抓虾

  5. 2009/11/15 20:07 | #5

    沙发是这样做的。呵呵

  6. sking
    2010/08/26 22:13 | #6

    高手 呵呵

  7. jasin2008
    2010/12/18 18:41 | #7

    大侠有什么好的参考资料推荐么

    • 2010/12/20 21:09 | #8

      网上有很多啊,你google一下就找到了。
      当然,也可以去 oracle.com, wikipedia.com 搜…

  8. 2011/04/09 21:09 | #9

    也看过,包括JDK的AQS的源码
    基本看完了楼主的前几篇并发相关的文章,其实就是里面的内容
    但这篇轻量级锁没看到过

    反反复复看了三遍,发现还是没看明白
    总的来说,觉得博主有几个问题没讲清楚
    1.文中说:
    “轻量级锁(Lightweight Locking)本意是为了减少多线程进入互斥的几率,并不是要替代互斥。
    它利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG),尝试在进入互斥前,进行补救。

    但其实博文中并没有描述怎么补救的,也没有解释怎么能减少进入互斥的几率了
    2.博文中的图片上的处理逻辑,仔细看了,也没发现轻量级锁的优越性体现在哪里了,跟普通锁比较感觉不到区别(我自己没了解过虚拟机层次普通锁的实现,只是从表现上推断),都是获取锁,失败等待,成功运行。
    3.个人感觉,文中的所谓的轻量级锁的原理,其实也是归结为对一个变量的CAS操作而已,跟AQS的原理其实没什么区别
    (当然,这是小弟个人的看法,还希望博主对这几个问题解释一下,感激不尽)

    • 2011/04/13 21:58 | #10

      1,怎么补救,就是进入mutex前,compare一下obj的mark word状态。确认该mark是否被其他线程持有。
      此时如果线程已经释放了mark,那么通过CAS后就可以直接进入线程,无需进入mutex.这就这个作用。

      2,轻量级锁只是普通锁的一个优化。就是堆一个变量的CAS操作。你理解对的。

  9. 2011/04/09 21:10 | #11

    上面第一行“也看过”后面应该是concurrent in practice,不知道怎么,加了个书名号就显示不出来了

  10. snake1987
    2011/04/14 09:03 | #12

    谢谢您的回答

Leave a comment

Attention: Java syntax highlighting is enable. For example: <pre lang="java"> class A {} </pre>