synchronized的底层原理实现以及优化的轻量级锁以及偏向锁

synchronized的底层原理实现以及优化

这里是对前面并发学习的一个补充

首先,先来普及下对象头信息,如下:

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第1张图片

其中Mark Word的结构如下:

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第2张图片

1、Monitor锁

这个是最原始的加锁方式,也称为重量级锁

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第3张图片

  • 刚开始Monitor中的owner为null
  • 当Thread-2执行到同步代码块时,就会将Owner置为Thread-2
  • 另一个线程过来想占用锁,检测到这个锁对象的Monitor的Owner被占用,到Monitor中的EntryList中阻塞等待。等Thread-2线程完成操作释放Owner时随机从EntryList中取出阻塞线程执行同步代码块,占用Owner。
  • WaitSet中的线程是之前获取锁的,但条件不满足进入Waiting状态的线程,即调用了wait()方法后进入等待状态,释放锁。

2、轻量级锁

当一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以用轻量级锁来优化

轻量级锁对用户是透明的,即语法依旧是synchronized

举如下例子,如下有两个同步代码块,利用同一个对象加锁

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第4张图片

此时的执行语句如下:

  • 创建锁记录对象(Lock Record),每个线程的栈帧都会包含锁记录结构,内部可以存储锁定对象的mark work
    synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第5张图片

  • 当线程到达method1同步代码块时,让锁记录中Object reference指向锁对象,并尝试cas替换锁对象的mark work,将mark word的值存入锁记录

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第6张图片

  • 如果此时的对象中的mark word的记录标识为01,表示无锁状态,则替换。如果此时其它线程已经占有了轻量级锁,则进入锁膨胀,如果自己占有这把锁,那么再添加一条Lock Record作为重入的标识,object reference依旧指向object对象,此时的锁记录为null

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第7张图片

  • 当退出同步代码块时,如果锁记录为null,直接出栈,表示重入计数减一

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第8张图片

  • 当退出同步代码块时,锁记录不为null,这时使用cas将mark word的值恢复给对象头
    • 成功,解锁成功
    • 不成功,说明有其它线程要进入同步代码块,锁已经发生锁膨胀,进入重量级解锁流程

锁膨胀

在上面实例中,如果有线程(Thread-0)已经占有了轻量级锁,另一个线程(Thread-1)再次进入同步代码块,检测到对象的mark work的标识为00,则会发生锁膨胀,将锁转变为重量级锁。为object申请Monitor锁,并让mark word指向Monitor,自己进入Monitor对象的entryList进行等待,此时Ower指向锁记录对象的对象引用

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第9张图片

当Thread-1执行完同步代码块的代码,使用cas将Mark word的值恢复给对象头,此时会失败。这时为进入重量级解锁流程,按照Monitor地址找到Monitor对象,将Owner设置为null,并唤醒EntryList中等待的线程。

自旋

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当自旋成功(即这个时候锁线程已经推出了同步块,释放了锁),这时当前线程就不会阻塞。

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第10张图片

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第11张图片

3、偏向锁

轻量级锁在没有竞争的时候,每次重入仍然需要执行cas(建立一个锁记录对象)

此时jdk6引入偏向锁,第一次即使用cas将线程的id设置到mark word头,之后发现这个线程的id为自己的表示没有竞争,不用重写cas,以后只要不发生竞争,这个对象就归线程所有

一个对象创建的时候:

  • 一个对象的创建默认就是开启偏向锁,此时mark word值为0x05即最后三位为101,这时它的thread、epoch、age都为0
  • 此时一个线程进入同步代码块,mark word的值将会记录其的线程id,以后每次进入同步代码块,确认是同一个线程所占用锁,便可直接进入同步代码块
  • 如果没有开启偏向锁,那么对象创立后,markword值为0x01即最后3位为001,这时它的hashcode、age都为0,第一次用到hashcode才会赋值

偏向锁失效的情况:

  • 调用对象的hashcode,偏向锁对象markword中存储的是线程id,如果调用对象的hashcode会导致偏向锁被撤销

轻量级锁会在所记录中记录hashcode

重量级锁会在monitor中记录hashcode

  • 撤销,当其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第12张图片

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第13张图片

  • 调用wait/notify

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第14张图片

img

偏向锁批量重偏向

对象虽然被多个线程访问,但没有竞争,这时偏向了线程t1线程的对象仍有机会偏向t2,重偏向会重置对象的Thread ID

当撤销偏向锁的阈值超过20次,jvm会给这些对象加锁时重新偏向至加锁线程,即前20个对象的锁对象变为轻量级锁,后面的对象mark work记录此次调用它的Thread id,实现偏向锁重偏向

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第15张图片

偏向锁批量撤销:

当偏向锁阈值达到40次后,jvm会将整个类的所有对象都改位不可偏向的,新键的对象也是不可偏向

如上图所示,此时又来个线程访问对象,循环40次,此时前20个锁对象依旧为轻量级锁,后面20个锁对象由偏向锁变为轻量级级锁,此时再创建对象,默认的偏向锁机制将改变,创建的对象mark word标识为001,即无锁状态。

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第16张图片

锁消除:

synchronized的底层原理实现以及优化的轻量级锁以及偏向锁_第17张图片

如上图所示,此时的锁对象o对于每个线程来说都不同,此时编译时JIT编译器优化将会把锁取除。

总结:锁可以升级, 但不能降级. 即: 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁是单向的.


你可能感兴趣的:(synchronized的底层原理实现以及优化的轻量级锁以及偏向锁)