偏向锁究竟是怎么回事啊啊啊啊

微信公众号:IT一刻钟 。大型现实非严肃主义现场 一刻钟与你分享优质技术架构与见闻,作一个有剧情的程序员。 关注可第一时间了解更多精彩内容,按期有福利相送哟。程序员

话说有这么一件事。安全

因而当天夜里,小哥哥便哼哧哼哧的画出了偏向锁的逻辑图。 其逻辑呢,各位看官待我慢慢道来。微信

看一张大图

点击看大图多线程

流程讲解

当JVM启用了偏向锁模式(JDK6以上默认开启),新建立对象的Mark Word中的Thread Id为0,说明此时处于可偏向但未偏向任何线程,也叫作匿名偏向状态(anonymously biased)。架构

偏向锁逻辑性能

1.线程A第一次访问同步块时,先检测对象头Mark Word中的标志位是否为01,依此判断此时对象锁是否处于无所状态或者偏向锁状态(匿名偏向锁);spa

2.而后判断偏向锁标志位是否为1,若是不是,则进入轻量级锁逻辑(使用CAS竞争锁),若是是,则进入下一步流程;线程

3.判断是偏向锁时,检查对象头Mark Word中记录的Thread Id是不是当前线程ID,若是是,则代表当前线程已经得到对象锁,之后该线程进入同步块时,不须要CAS进行加锁,只会往当前线程的栈中添加一条Displaced Mark Word为空的Lock Record中,用来统计重入的次数(如图为当对象所处于偏向锁时,当前线程重入3次,线程栈帧中Lock Record记录)。 3d

退出同步块释放偏向锁时,则依次删除对应Lock Record,可是不会修改对象头中的Thread Id;对象

注:偏向锁撤销是指在获取偏向锁的过程当中因不知足条件致使要将锁对象改成非偏向锁状态,而释放是指退出同步块时的过程。

4.若是对象头Mark Word中Thread Id不是当前线程ID,则进行CAS操做,企图将当前线程ID替换进Mark Word。若是当前对象锁状态处于匿名偏向锁状态(可偏向未锁定),则会替换成功(将Mark Word中的Thread id由匿名0改为当前线程ID,在当前线程栈中找到内存地址最高的可用Lock Record,将线程ID存入),获取到锁,执行同步代码块;

5.若是对象锁已经被其余线程占用,则会替换失败,开始进行偏向锁撤销,这也是偏向锁的特色,一旦出现线程竞争,就会撤销偏向锁;

6.偏向锁的撤销须要等待全局安全点(safe point,表明了一个状态,在该状态下全部线程都是暂停的),暂停持有偏向锁的线程,检查持有偏向锁的线程状态(遍历当前JVM的全部线程,若是能找到,则说明偏向的线程还存活),若是线程还存活,则检查线程是否在执行同步代码块中的代码,若是是,则升级为轻量级锁,进行CAS竞争锁;

注:每次进入同步块(即执行monitorenter)的时候都会以从高往低的顺序在栈中找到第一个可用的Lock Record,并设置偏向线程ID;每次解锁(即执行monitorexit)的时候都会从最低的一个Lock Record移除。因此若是能找到对应的Lock Record说明偏向的线程还在执行同步代码块中的代码。

7.若是持有偏向锁的线程未存活,或者持有偏向锁的线程未在执行同步代码块中的代码,则进行校验是否容许重偏向,若是不容许重偏向,则撤销偏向锁,将Mark Word设置为无锁状态(未锁定不可偏向状态),而后升级为轻量级锁,进行CAS竞争锁;

8.若是容许重偏向,设置为匿名偏向锁状态,CAS将偏向锁从新指向线程A(在对象头和线程栈帧的锁记录中存储当前线程ID)

9.唤醒暂停的线程,从安全点继续执行代码

以上即是偏向锁的整个逻辑了。

延申知识

批量重偏向与批量撤销

渊源:从偏向锁的加锁解锁过程当中可看出,当只有一个线程反复进入同步块时,偏向锁带来的性能开销基本能够忽略,可是当有其余线程尝试得到锁时,就须要等到safe point时,再将偏向锁撤销为无锁状态或升级为轻量级,会消耗必定的性能,因此在多线程竞争频繁的状况下,偏向锁不只不能提升性能,还会致使性能降低。 因而,就有了批量重偏向与批量撤销的机制。

解决场景:

批量重偏向(bulk rebias)机制是为了解决:一个线程建立了大量对象并执行了初始的同步操做,后来另外一个线程也来将这些对象做为锁对象进行操做,这样会致使大量的偏向锁撤销操做。

批量撤销(bulk revoke)机制是为了解决:在明显多线程竞争剧烈的场景下使用偏向锁是不合适的。

原理: 以class为单位,为每一个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操做时,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就认为该class的偏向锁有问题,所以会进行批量重偏向。

每一个class对象会有一个对应的epoch字段,每一个处于偏向锁状态对象的Mark Word中也有该字段,其初始值为建立该对象时class中的epoch的值。

每次发生批量重偏向时,就将该值+1,同时遍历JVM中全部线程的栈,找到该class全部正处于加锁状态的偏向锁,将其epoch字段改成新值。下次得到锁时,发现当前对象的epoch值和class的epoch不相等,那就算当前已经偏向了其余线程,也不会执行撤销操做,而是直接经过CAS操做将其Mark Word的Thread Id 改为当前线程Id。

当达到重偏向阈值后,假设该class计数器继续增加,当其达到批量撤销的阈值后(默认40),JVM就认为该class的使用场景存在多线程竞争,会标记该class为不可偏向,以后,对于该class的锁,直接走轻量级锁的逻辑。

相关文章
相关标签/搜索