
Java轻量级锁(自旋锁)和偏向锁原理.doc
6页大家知道,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的作用是为了在接下里的运行过程中,识别哪个对象被锁住了下图直观地描述了交换指针的操作最后一步unlock中,我们发现,JVM同样使用了CAS来验证object mark word在持有锁到释放锁之间,有无被其他线程访问如果其他线程在持有锁这段时间里,尝试获取过锁,则可能自身被挂起,而mark word的重量级锁指针也会被相应修改此时,unlock后就需要唤醒被挂起的线程阅读本文的读者,需要对Java轻量级锁有一定的了解,知道lock record, mark word之类的名词可以参考我的一篇博文:Java轻量级锁原理详解(Lightweight Locking)Java偏向锁(Biased Locking)是Java6引入的一项多线程优化它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能轻量级锁也是一种多线程优化,它与偏向锁的区别在于,轻量级锁是通过CAS来避免进入开销较大的互斥操作,而偏向锁是在无竞争场景下完全消除同步,连CAS也不执行(CAS本身仍旧是一种操作系统同步原语,始终要在JVM与OS之间来回,有一定的开销)。
所谓的无竞争场景,举个例子,就是单线程访问带同步的资源或方法偏向锁实现原理偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁偏向锁只能在单线程下起作用)通过下图可以更直观的理解偏向锁:这张图,省略了轻量级锁相关的几处步骤,将关注点更多地聚焦在偏向锁的状态变化上偏向模式和非偏向模式,在下面的mark word表中,主要体现在thread ID字段是否为空挂起持有偏向锁的线程,这步操作类似GC的pause,但不同之处是,它只挂起持有偏向锁的线程(非当前线程)在抢占模式的橙色区域说明中有提到,指向当前堆栈中最近的一个Lock record(在轻量级锁原理一文有讲到,lock record是进入锁前会在stack上创建的一份内存空间)这里提到的最近的一个lock record,其实就是当前锁所在的stack frame上分配的lock record整个步骤是从偏向锁恢复到轻量级锁的过程。
偏向锁也会带来额外开销在JDK6中,偏向锁是默认启用的它提高了单线程访问同步资源的性能但试想一下,如果你的同步资源或代码一直都是多线程访问的,那么消除偏向锁这一步骤对你来说就是多余的事实上,消除偏向锁的开销还是蛮大的所以在你非常熟悉自己的代码前提下,大可禁用偏向锁 -XX:-UseBiasedLocking 。
