非公平锁的获取流程,允许插队,直接尝试CAS操作,尝试失败时才执行入队。竞争激烈的环境中,非公平锁直接尝试锁定操作可以提高系统吞吐量。如果当前线程请求锁时,正好锁刚刚被释放,其他等待线程还没有被唤醒,在这过程中该线程完成了相关操作并释放锁,此时其他线程刚好被唤醒。利用线程唤醒的延时,其他线程能够完成操作,且不影响该阻塞线程唤醒后获取锁。
非公平锁在请求获取锁时,直接尝试CAS操作获取锁,如果尝试失败才进行排队。在竞争激烈的情况下,非公平锁的性能高于公平锁的一个原因是:活动线程直接尝试获取锁的操作时间可能要比恢复一个阻塞线,并把锁分配给它的时间短的多(线程的唤醒操作需要额外的时间,从唤起到线程真正运行之间存在着严重的时延)。
轻量级锁也是一种多线程优化,它与偏向锁的区别在于,轻量级锁是通过CAS来避免进入开销较大的互斥操作,而偏向锁是在无竞争场景下完全消除同步,连CAS也不执行(CAS本身仍旧是一种操作系统同步原语,始终要在JVM与OS之间来回,有一定的开销)。
轻量级锁(LightweightLocking)本意是为了减少多线程进入互斥的几率,并不是要替代互斥。
它利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG),尝试在进入互斥前,进行补救。
轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的MarkWord复制到锁记录中,官方称为DisplacedMark Word。然后线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
轻量级锁解锁:轻量级解锁时,会使用原子的CAS操作来将DisplacedMark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,导致锁膨胀的流程图。
讲了synchronized锁的具体实现,就将偏向锁,自旋锁,轻量级锁,重量级锁 这些概念串起来了。偏向锁,在上篇博客中已经有所讲到,因此,这篇博客重点讲下偏向锁获取失败后的步骤。
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 record:是一块分配在线程堆栈上的空间区域,因此每个线程都有自己的一个lock record。
object mark word 里的轻量级锁指针指向是会指向真正的锁的拥有者的lock record所在的stack指针,获取锁的线程的lock record的owner字段会指向object mark word,作用是为了在接下里的运行过程中,识别哪个对象被锁住了。
