锁升级?synchronized优化了解吗?

了解锁升级,得先知道,不同锁的状态是什么样的。这个状态指的是什么呢?

Java对象头里,有一块结构,叫Mark Word标记字段,这块结构会随着锁的状态变化而变化。

Mark Word存储对象自身的运行数据,如哈希码、GC分代年龄、锁状态标志、偏向时间戳(Epoch) 等。

synchronized做了哪些优化?

在JDK1.6之前,synchronized的实现直接调用ObjectMonitor的enter和exit,这种锁被称之为重量级锁。从JDK6开始,HotSpot虚拟机开发团队对Java中的锁进行优化,如增加了适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等优化策略,提升了synchronized的性能。

  • 偏向锁:在无竞争的情况下,只是在Mark Word里存储当前线程指针,CAS操作都不做。
  • 轻量级锁:在没有多线程竞争时,相对重量级锁,减少操作系统互斥量带来的性能消耗。但是,如果存在锁竞争,除了互斥量本身开销,还额外有CAS操作的开销。
  • 自旋锁:减少不必要的CPU上下文切换。在轻量级锁升级为重量级锁时,就使用了自旋加锁的方式
  • 锁粗化:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。
  • 锁消除:虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。

锁升级的过程是什么样的?

锁升级方向:无锁-->偏向锁---> 轻量级锁---->重量级锁,这个方向基本上是不可逆的。

1.png

偏向锁:

偏向锁的获取:

  1. 判断是否为可偏向状态--MarkWord中锁标志是否为‘01’,是否偏向锁是否为‘1’
  2. 如果是可偏向状态,则查看线程ID是否为当前线程,如果是,则进入步骤'5',否则进入步骤‘3’
  3. 通过CAS操作竞争锁,如果竞争成功,则将MarkWord中线程ID设置为当前线程ID,然后执行‘5’;竞争失败,则执行‘4’
  4. CAS获取偏向锁失败表示有竞争。当达到safepoint时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块
  5. 执行同步代码

偏向锁的撤销:

  1. 偏向锁不会主动释放(撤销),只有遇到其他线程竞争时才会执行撤销,由于撤销需要知道当前持有该偏向锁的线程栈状态,因此要等到safepoint时执行,此时持有该偏向锁的线程(T)有‘2’,‘3’两种情况;
  2. 撤销----T线程已经退出同步代码块,或者已经不再存活,则直接撤销偏向锁,变成无锁状态----该状态达到阈值20则执行批量重偏向
  3. 升级----T线程还在同步代码块中,则将T线程的偏向锁升级为轻量级锁,当前线程执行轻量级锁状态下的锁获取步骤----该状态达到阈值40则执行批量撤销

轻量级锁:

轻量级锁的获取:

  1. 进行加锁操作时,jvm会判断是否已经时重量级锁,如果不是,则会在当前线程栈帧中划出一块空间,作为该锁的锁记录,并且将锁对象MarkWord复制到该锁记录中
  2. 复制成功之后,jvm使用CAS操作将对象头MarkWord更新为指向锁记录的指针,并将锁记录里的owner指针指向对象头的MarkWord。如果成功,则执行‘3’,否则执行‘4’
  3. 更新成功,则当前线程持有该对象锁,并且对象MarkWord锁标志设置为‘00’,即表示此对象处于轻量级锁状态
  4. 更新失败,jvm先检查对象MarkWord是否指向当前线程栈帧中的锁记录,如果是则执行‘5’,否则执行‘4’
  5. 表示锁重入;然后当前线程栈帧中增加一个锁记录第一部分(Displaced Mark Word)为null,并指向Mark Word的锁对象,起到一个重入计数器的作用。
  6. 表示该锁对象已经被其他线程抢占,则进行自旋等待(默认10次),等待次数达到阈值仍未获取到锁,则升级为重量级锁

完整的升级过程:

2.png

标签: java, Java面试题, Java问题合集, Java编程, Java问题精选, Java常见问题