阿里的人问什么是锁膨胀,答不上来,回来作了总结:html
关于锁的膨胀,synchronized的原理参考:深刻分析Synchronized原理(阿里面试题)面试
首先说一下锁的优化策略。编程
1,自旋锁并发
自旋锁其实就是在拿锁时发现已经有线程拿了锁,本身若是去拿会阻塞本身,这个时候会选择进行一次忙循环尝试。也就是不停循环看是否能等到上个线程本身释放锁。这个问题是基于一个现实考量的:不少拿了锁的线程会很快释放锁。由于通常敏感的操做不会不少。固然这个是一个不能彻底肯定的状况,只能说整体上是一种优化。post
举个例子就比如一我的要上厕所发现厕所里面有人,他能够:1,等一小会。2,跑去另外的地方上厕所。等一小会不必定能等到前一我的出来,不过若是跑去别的厕所的花费的时间确定比等一小会结果前一我的出来了长。固然等完告终果那我的没出来仍是要跑去别的地方上厕所这是最慢的。优化
而后是基于这种作法的一个优化:自适应自旋锁。也就是说,第一次设置最多自旋10次,结果在自旋的过程当中成功得到了锁,那么下一次就能够设置成最多自旋20次。道理是:一个锁若是可以在自旋的过程当中被释放说明颇有可能下一次也会发生这种事。那么就更要给这个锁某种“便利”方便其不阻塞得锁(毕竟快了不少)。一样若是屡次尝试的结果是彻底不能自旋等到其释放锁,那么就说明颇有可能这个临界区里面的操做比较耗时间。就减少自旋的次数,由于其可能性过小了。url
2,锁粗化spa
试想有一个循环,循环里面是一些敏感操做,有的人就在循环里面写上了synchronized关键字。这样确实没错不过效率也许会很低,由于其频繁地拿锁释放锁。要知道锁的取得(假如只考虑重量级MutexLock)是须要操做系统调用的,从用户态进入内核态,开销很大(阿里面试)。因而针对这种状况也许虚拟机发现了以后会适当扩大加锁的范围(因此叫锁粗化)以免频繁的拿锁释放锁的过程。操作系统
3,锁消除线程
经过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),而“自做多情”地给本身加上了锁。有可能虚拟机会直接去掉这个锁。
4,偏向锁和轻量级锁
这两个锁既是一种优化策略,也是一种膨胀过程因此一块儿说。首先它们的关系是:最高效的是偏向锁,尽可能使用偏向锁,若是不能(发生了竞争)就膨胀为轻量级锁,这样优化的效率不如原来高不过仍是一种优化(对比重量级锁而言)。因此整个过程是尽量地优化。
首先说说偏向锁。
HotSpot的研究人员发现大多数状况下虽然加了锁,可是没有竞争的发生,甚至是同一个线程反复得到这个锁。那么偏向锁就为了针对这种状况。
举个例子,一个仓库管理员管着钥匙,然而每一次都是老王去借,仓库管理员因而就认识了老王,直接和他说,“行,你直接拿就是不用填表格了我记得你”。
讲一下偏向锁的具体过程。首先JVM要设置为可用偏向锁。而后当一个进程访问同步块而且得到锁的时候,会在对象头和栈帧的锁记录里面存储取得偏向锁的线程ID。
下一次有线程尝试获取锁的时候,首先检查这个对象头的MarkWord是否是储存着这个线程的ID。若是是,那么直接进去而不须要任何别的操做。若是不是,那么分为两种状况。1,对象的偏向锁标志位为0(当前不是偏向锁),说明发生了竞争,已经膨胀为轻量级锁,这时使用CAS操做尝试得到锁(这个操做具体是轻量级锁的得到锁的过程下面讲)。2,偏向锁标志位为1,说明仍是偏向锁不过请求的线程不是原来那个了。这时只须要使用CAS尝试把对象头偏向锁从原来那个线程指向目前求锁的线程。这种状况举个例子就是老王准备退休了,他儿子接替他来拿钥匙,因而仓库管理员认识了他儿子,他儿子每次来也不用登记注册了。
这个CAS失败了呢?首先必须明确这个CAS为何会失败,也就是说发生了竞争,有别的线程和它抢锁而且抢赢了,那么这个状况下,它就会要求撤销偏向锁(由于发生了竞争)。接着它首先暂停拥有偏向锁的线程,检查这个线程是不是个活动线程,若是不是,那么好,你拿了锁可是没在干事,锁还记录着你,那么直接把对象头设置为无锁状态从新来过。若是仍是活动线程,先遍历栈帧里面的锁记录,让这个偏向锁变为无锁状态,而后恢复线程。
再说轻量级锁。这是偏向锁膨胀以后的产物。
加锁的过程:JVM在当前线程的栈帧中建立用于储存锁记录的空间(LockRecord),而后把MarkWord放进去,同时生成一个叫Owner的指针指向那个加锁的对象,同时用CAS尝试把对象头的MarkWord成一个指向锁记录的指针。至于synchronized的原理 也是阿里问的热点问题
成功了就拿到了锁。那么失败了呢?失败了的说法比较多。主流有《深刻理解JVM》的说法和《并发编程的艺术》的说法。
《深刻理解JVM》的说法:
失败了,去查看MarkWord的值。有2种可能:1,指向当前线程的指针,2,别的值。
若是是1,那么说明发生了“重入”的状况,直接当作成功得到锁处理。
其实这个有个疑问,为何得到锁成功了而CAS失败了,这里其实要牵扯到CAS的具体过程:先比较某个值是否是预测的值,是的话就动用原子操做交换(或赋值),不然不操做直接返回失败。在用CAS的时候期待的值是其本来的MarkWord。发生“重入”的时候会发现其值不是期待的本来的MarkWord,而是一个指针,因此固然就返回失败,可是若是这个指针指向这个线程,那么说明其实已经得到了锁,不过是再进入一次。若是不是这个线程,那么状况2:
若是是2,那么发生了竞争,锁会膨胀为一个重量级锁(MutexLock)
《并发编程的艺术》的说法:
失败了直接自旋。指望在自旋的时间内得到锁,若是仍是不能得到,那么开始膨胀,修改锁的MarkWord改成重量级锁的指针,而且阻塞本身。
解锁过程:(那个拿到锁的线程)用CAS把MarkWord换回到原来的对象头,若是成功,那么没有竞争发生,解锁完成。若是失败,表示存在竞争(以前有线程试图经过CAS修改MarkWord),这时要释放锁而且唤醒阻塞的线程。