Java锁膨胀

前言

到这里,大家应该都知道,Java中都有哪些锁,做什么用的了;

那么,不知道大家有没有听过Java锁的膨胀机制?

synchronized

JDK1.6 前只有重量级锁,JDK1.6之后,jvm对其进行优化,增加了偏向锁和轻量级锁(这两种锁咱们在上一篇已经解释过了,就不在复述了);

对象头

了解锁膨胀之前,咱们需要先了解下对象头;

在JVM的实现中每一个对象都会有一个对象头,它是用于保存对象的系统信息,对象头中有一个官方称为 MarkDown 的部分,他就是实现各种锁的关键;

在32位系统里,MarkDown 是32位的数据,64位系统中是64位的数据;

存放了对象的哈希值、对象年龄、锁的指针等等信息,总之就是一个对象的锁是否被占用、占用的是哪种锁,就记录在MarkDown中;

synchronized 升级之一阶段(偏向锁)

当线程访问同步代码块并获取锁时,会在 Mark Word 里存储锁偏向的线程 ID;

在线程进入和退出同步块时不再通过 CAS 操作来加锁和解锁,而是检测 Mark Word 里是否存储着指向当前线程的偏向锁;

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的;

偏向锁的撤销:

  1. 在某个时间点上没有字节码正在执行时,它会先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态
  2. 线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁,恢复到无锁(标志位为01)或轻量级锁(标志位为00)的状态

JVM 参数:

关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态

synchronized 升级之二阶段(轻量级锁)

轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能;

升级过程:

  1. 线程在自己的栈桢中创建锁记录 LockRecord
  2. 将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中
  3. 将锁记录中的Owner指针指向锁对象
  4. 将锁对象的对象头的MarkWord替换为指向锁记录的指针,并将对象 Mark Word 的锁标志位设置为“00”,表示此对象处于轻量级锁定状态

出现轻量级锁的两种情况:

  1. 当关闭偏向锁功能时
  2. 多个线程竞争偏向锁导致偏向锁升级为轻量级锁

轻量级锁: CAS 操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能;

轻量级锁再次膨胀情况:

  1. 若当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁
  2. 当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁

synchronized 升级之三阶段(重量级锁)

重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被成为互斥锁;

当有一个线程获取重量级锁之后,其余所有等待获取该锁的线程都会处于阻塞状态,操作系统实现线程之间的切换需要从用户态切换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长,所以它的切换成本非常高,这同时也说明了为什么重量级线程开销很大的原因;

总结

通过上面的分析,我们知道了 synchronized 锁的演变过程,这就告诉我们,假如一开始就知道某个同步代码块的竞争很激烈,那么一开始就应该使用重量级锁了,节省掉一些锁转换的开销。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容