synchronized锁的升级和优化

198 阅读5分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第3篇文章,点击查看活动详情

前言

jdk1.6之前的版本synchronized属于重量级锁,因为线程的上下文切换需要从用户态转换到内核态,这个过程非常消耗CPU性能和耗费大量时间。但是在jdk1.6之后,jvm对于synchronized进行相关的优化,引入了偏向锁和轻量级锁,大大减少了synchronized的锁带来的性能消耗。本文将重点讲解synchronized锁的升级和优化。

Synchronized锁的膨胀升级过程

锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。

图片.png

偏向锁

偏向锁是这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁一直没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。

作用

减少只有一个线程执行同步代码块时的性能消耗,即在没有其他线程竞争的情况下,一个线程获得了锁。

原理

使用CAS操作将当前线程的ID记录到对象的Mark Word中。

获取偏向锁流程

偏向锁.webp

  • 检查对象头中Mark Word锁的状态,如果为无锁状态,是否开启偏向锁,如果未开启则直接升级轻量级锁。
  • 如果为有锁状态,判断Mark Work中的线程ID是否指向当前线程,如果是则执行同步代码块。
  • 如果不是,则进行CAS操作竞争锁,如果竞争成功到锁,则将Mark Work中的线程ID设为当前线程ID,执行同步代码块。如果竞争失败,升级为轻量级锁。

jvm如何开启偏向锁

虚拟机启用了偏向锁(启用参数-XX:+UseBiased Locking,这是自JDK 6起HotSpot虚拟机的默认值)

轻量级锁

当偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word的结构也变为轻量级锁的结构。

作用

在多线程交替执行同步代码块时(未发生竞争),避免使用互斥量带来的性能消耗。但多个线程同时进入临界区(发生竞争)则会使得轻量级锁膨胀为重量级锁。

获取轻量级流程

  1. 首先判断当前对象是否处于一个无锁的状态,如果是Java虚拟机将在当前线程的栈帧建立一个锁记录(Lock Record),用于存储对象目前的MarkWord的拷贝,如图所示。

图片.png

  1. 将对象的Mark Word复制到栈帧中的Lock Record中,并将Lock Record中的owner指向当前对象,并使用CAS操作将对象的Mark Word更新为指向Lock Record的指针,如图所示。

图片.png

  1. 如果执行成功,即代表该线程拥有了这个对象的锁,并且对象Mark Word的锁标志位将转变为“00”,表示此对象处于轻量级锁定状态。

  2. 如果执行失败,说明有一条线程与当前线程竞争获取该对象的锁。虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了这个对象的锁,那直接执行同步块,否则就说明这个锁对象已经被其他线程抢占了。如果出现两条以上的线程争用同一个锁的情况,那轻量级锁就不再有效,必须要膨胀为重量级锁,锁标志的状态值变为“10”,此时Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也必须进入阻塞状态。

重量级锁

当轻量级锁通过自旋获取失败后将会升级到重量级锁,当多个线程访问同步代码块时,相当于去争抢对象监视器修改对象中的锁标识。对象头MarkWord中可以通过monitor()方法获ObjectMonitor的指针,也就是前面以32位虚拟机举例时MarkWord前30位变成了指向ObjectMonitor的指针。

偏向锁、轻量级锁、重量级锁对比

图片.png

锁的优化

锁的消除

锁消除是指Java虚拟机在即时编译时,通过对运行上下的扫描,消除那些不可能存在共享资源竞争的锁。锁消除可以节约无意义的请求锁时间。消除的依据来自于JVM会判断在一段程序中的需要同步的数据 明显不会逃逸出去 从而被其他线程访问到,那JVM就把它们当作栈上数据对待,认为这些数据是线程独有的,不需要加同步。此时就会进行锁消除。

锁的粗化

原则上需要将同步块的作用范围限制得尽量小。但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体之中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。例如如下代码:

for(int j=0;j<10;j++)
{
    synchronized(lock)
    {
      system.out.println(j);
    }
}

说明:通过循环频繁的加锁和解锁导致了锁的粗化。

总结

本文对于synchronized锁的升级和优化进行详细的讲解,如有疑问请随时反馈。