我是一个线程,生活在JVM(Java虚拟机)中,这几天有点无聊,全世界好像只有一个人,每天忙着执行代码,很难休息.听说人写的代码有一些特别的地方,叫临界区,比如synchronized修饰的方法或者代码块,很神奇,JVM老大只允许一个线程同时进入执行。其实老大设置了一把锁,抢到锁后才能执行,否则只能阻塞等待别人释放锁。老板说堵就是不用干活,老老实实等着。有那么美好的东西!让我现在阻止它。但是老大却说:“我每次上锁,都要跟操作系统打交道,让他在内核维护一个互斥量(mutex),他还要阻塞和切换你的线程,这是一笔财富。”巨大的成本,所以这些锁应该谨慎使用。”我也是倒霉,不知道执行了多少代码,调用了多少函数,一次临界区都没遇到!我想可能是这个程序员编程时注意不要考虑多线程并发的情况;也有可能这些程序大部分都是无状态的,执行多少个线程都没有问题。所以我不得不继续执行它。不知过了多少天,激动的发现终于出现了synchronized修改的代码块:accountaccount=...synchronized(account){...临界区中的代码...}对于偏向锁,希望其他线程已经进入代码块,这样我就可以阻塞休息了。就算没有其他线程进入临界区,老大给我申请锁,我也得和操作系统协商互斥量,从用户态进入核心态,再从用户态回到用户态核心状态。这需要一些努力。但是老大根本不找操作系统,只是看账户对象的所谓“对象头”。有个东西叫MarkWord,好像是某种数据结构,里面有几个标识位和其他数据。老板用CAS操作把我的线程ID记录到这个MarkWord里,修改了flag,然后告诉我:没关系,你现在有这个锁了,进去执行代码吧。我惊讶道:“老大,你不和操作系统协商设置Mutex?”有了这个锁,操作系统就不需要干预了。”拿到锁开始执行synchronized包裹的代码块,当我第二次执行这个synchronized的时候,老大只是看了一眼锁对象账号的MarkWord说:“你的线程ID还在,它仍然在那里。持有这个对象的锁,进入临界区执行。”连喘口气的机会都没有,只好继续执行。老大说,这叫偏向锁,当没有其他线程竞争的时候,它总是偏袒我,让我保持执行。多么期待一个新的。和我比一比!轻量级锁速度快,机会来了。另一个线程0x3704也需要进入这个代码块执行,但是锁对象账户存储了我的线程ID,所以他不能进入criticalArea。我心想,至少我们两个中的一个必须进入阻塞状态,休息一会儿。但是老大还是没有和操作系统交涉,而是说:我把这个偏向锁升级一下,变成轻量级锁吧。老大把锁对象账号恢复到解锁状态,在我们每个栈帧里分配一个空间,叫做LockRecord,把锁对象账号的MarkWord复制到我们每一个人,都叫做DisplacedMarkWord,这个名字好奇怪.然后我把我的LockRecord的地址用CAS放到MarkWord中,把lockflag改成了00,其实也就意味着我也得到了这个轻量级的锁。可以继续进入临界区执行。0x3704没有获得锁,但还是不阻塞。老板让他转几圈,等一会。当我退出临界区并释放锁时,我需要使用CAS将Displaced标记字复制回来。然后他可以锁定。我们两个人交替进入临界区,执行这段代码。没有问题,真正的竞争很少。即使有竞争,想要获取锁的线程只需要自旋几次,稍等片刻,锁就有可能被释放。显然,如果没有竞争或轻度竞争,轻量级锁只使用CAS操作和Lock记录,避免了重量级互斥体的开销。对于JVM老大来说,真是个好主意。重量级锁和轻量级锁都很好用,我还是没机会休息,终于有一天,0x3704拿着锁,硬着头皮执行临界区的代码。旋转了很多次,0x3704还是没有释放锁。这时候JVM老大说:自旋太多了,很浪费CPU,那就升级为重量级锁吧!这种重量级锁需要操作系统的帮助,依赖于操作系统底层的互斥锁。看到老大创建了一个monitor对象,把这个对象的地址更新到Markword中。锁升级了!由于0x3704还在带锁运行,我终于进入了梦幻状态:阻塞!终于可以休息了!仔细想想,老大苦心设置了偏向锁和轻量级锁来避免阻塞,避免操作系统的干预。这两种锁无非是针对这两种情况:偏向锁:通常只有一个线程在临界区执行轻量级锁:多个线程可以交替进入临界区,在竞争不激烈的时候,可以获得锁通过旋转并等待一段时间。至于重量级锁,也是我最期待的锁,就是竞争激烈,所以要堵着休息。更正!Jvm高手,你傻傻的指出一个错误,锁对象头中并没有存放线程id,实际上存放的是JavaThread对象的指针地址。图片无法修改,请在此更正【本文为专栏作家“刘欣”原创稿件,转载请通过作者微信获得授权公众号编码】点此查看该作者更多好文
