http://blog.sina.com.cn/s/blog_c038e9930102v2ht.html
轻量级锁也是一种多线程优化,它与偏向锁的区别在于,轻量级锁是通过CAS来避免进入开销较大的互斥操作,而偏向锁是在无竞争场景下完全消除同步,连CAS也不执行(CAS本身仍旧是一种操作系统同步原语,始终要在JVM与OS之间来回,有一定的开销)。
轻量级锁(LightweightLocking)本意是为了减少多线程进入互斥的几率,并不是要替代互斥。
它利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG),尝试在进入互斥前,进行补救。
轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的MarkWord复制到锁记录中,官方称为DisplacedMark Word。然后线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
轻量级锁解锁:轻量级解锁时,会使用原子的CAS操作来将DisplacedMark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,导致锁膨胀的流程图。
在图中,提到了拷贝锁对象头的objectmark word,由于脱离了原始markword,官方将它冠以displaced前缀,即displacedmark word(置换标记字)。
这个displacedmark word是整个轻量级锁实现的关键,在CAS中的compare就需要用它作为条件。
为什么要拷贝mark word?其实很简单,原因是为了不想在lock与unlock这种底层操作上再加同步。
在拷贝完object markword之后,JVM做了一步交换指针的操作,即流程中第一个橙色矩形框内容所述。
============
将object mark word里的轻量级锁指针指向lockrecord所在的stack指针,作用是让其他线程知道,该objectmonitor已被占用(就像偏向锁中用CAS的方式将markword的id指向当前尝试获取锁的线程id,这里是将markword中的轻量级锁指针以CAS的方式尝试指向当前线程的lockrecord,这样别的线程便知道当前轻量锁已经指向别的线程了)。
lock record里的owner指针指向objectmark word的作用是为了在接下里的运行过程中,识别哪个对象被锁住了。
CAS 的是makeworld中的轻量级锁指针
================
下图直观地描述了交换指针的操作。
最后一步unlock中,我们发现,JVM同样使用了CAS来验证object markword在持有锁到释放锁之间,有无被其他线程访问。
如果其他线程在持有锁这段时间里,尝试获取过锁,则可能自身被挂起,而markword的重量级锁指针也会被相应修改。
释放锁线程视角:所以由轻量锁切换到重量锁,是发生在轻量锁释放锁的期间,之前在获取锁的时候它拷贝了锁对象头的markword,在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对markword做了修改,两者比对发现不一致,则切换到重量锁。
因为重量级锁被修改了,所有display mark word和原来的markword不一样了。
怎么补救,就是进入mutex前,compare一下obj的markword状态。确认该markword是否被其他线程持有。
此时如果线程已经释放了markword,那么通过CAS后就可以直接进入线程,无需进入mutex,就这个作用。
尝试获取锁线程视角:如果线程尝试获取锁的时候,轻量锁正被其他线程占有,那么它就会修改markword,修改重量级锁,表示该进入重量锁了。
还有一个注意点:等待轻量锁的线程不会阻塞,它会一直自旋等待锁,并如上所说修改markword。
这就是自旋锁,尝试获取锁的线程,在没有获得锁的时候,不被挂起,而转而去执行一个空循环,即自旋。在若干个自旋后,如果还没有获得锁,则才被挂起,获得锁,则执行代码。
因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。