当前位置: 首页 > 科技观察

Synchronized优化方式的锁扩展机制!

时间:2023-03-12 19:13:46 科技观察

本文转载自微信公众号“Java中文社区”,作者雷哥。转载本文请联系Java中文社区公众号。在JDK1.5之前,synchronized的性能比较低。那时候,我们通常选择使用Lock而不是synchronized。然而,这种情况在JDK1.6中得到了改变。在JDK1.6中,对synchronized做了各种优化,性能也有了很大的提升。这也是当前版本中经常看到synchronized的重要原因之一。当然,除了性能,synchronized的使用也很方便,这也是它流行的一个重要原因。在众多的优化方案中,锁扩展机制是最有利于提高synchronized性能的手段之一(其他优化方案我们后面会讲到)。在这篇文章中,我们关注什么是锁扩展?以及锁扩展的各种细节。正文在JDK1.5时,synchronized需要调用监控锁(Monitor)来实现。监听锁本质上是依赖底层操作系统的MutexLock(互斥锁)实现的。互斥锁在释放和获取中有时需要从用户态切换到内核态,成本高,执行时间长。这种依赖于操作系统MutexLock实现的锁被称为“重量级锁”。什么是用户态和内核态?用户模式:当一个进程正在执行用户自己的代码时,称它处于用户运行模式。内核态:当一个任务(进程)执行系统调用,落入内核代码的执行中,我们调用内核运行态的进程。此时,处理器以最高特权级在内核代码中执行。为什么分为内核态和用户态?假设不区分内核态和用户态,程序可以随意读写硬件资源,比如随意读写和分配内存,这样如果程序员不小心写了不合适的内容到不该写的地方写入,很可能导致系统崩溃。有了用户态和内核态的区分,程序在执行某项操作时会进行一系列的验证和检查,确认没有问题后才能正常操作资源,这样就不用担心误转了当系统崩溃时,即有了内核态和用户态的区分,程序可以更安全地运行,但同时,在两种模式之间切换会造成一定的性能开销。锁扩展在JDK1.6中,为了解决获取和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”两种状态。此时一共有四种同步状态:Lock-freebiasLocksLightweightlocksHeavyweightlocks锁的级别按照上面的顺序升级。我们称这个升级过程为“锁扩展”。PS:到现在为止,锁的升级都是单向的,也就是说只能从低到高升级(无锁->偏向锁->轻量级锁->重量级锁),不会有锁退化条件。为什么锁扩展可以优化synchronized的性能?当我们了解了这些锁状态之后,自然就有了答案。一起来看看吧。1.偏向锁HotSpot作者通过研究和实践发现,在大多数情况下,不存在多线程竞争锁的情况,锁总是由同一个线程多次获取。为了让线程获取锁的成本更低,引入了偏向锁。.偏向锁意味着它会偏向于第一个访问锁的线程。如果在运行过??程中只有一个线程访问同步锁,并且没有多线程争用,则不需要触发该线程。同步的,在这种情况下会在线程上加一个偏向锁。偏向锁执行流程当线程访问同步代码块并获得锁时,会将偏向锁线程ID存储在对象头的MarkWord中,线程进入和退出时不再通过CAS操作加锁解锁同步块。相反,它会检测是否有指向当前线程的偏向锁存储在MarkWord中。如果MarkWord中的线程ID与访问的线程ID相同,则可以直接进入同步块执行代码。如果线程ID不同,则使用CAS尝试获取Lock,获取成功则进入同步块执行代码,否则锁的状态将升??级为轻量级锁。偏向锁的优点偏向锁的设计目的是在没有多线程竞争的情况下,尽量减少不必要的锁切换,因为锁的获取和释放依赖于多条CAS原子指令,而偏向锁只需要更换线程执行即可CAS原子指令一次为ID。MarkWord扩展知识:内存布局在HotSpot虚拟机中,对象在内存中的布局可以分为以下三个区域:对象头(Header)实例数据(InstanceData)对齐填充(Padding)对象头包含:MarkWord(标记字段):我们的偏向锁信息就存放在这个区域。KlassPointer(类对象指针)对象在内存中的布局如下:在JDK1.6中,偏向锁默认开启,可以通过“-XX:-UseBiasedLocking=false”命令关闭偏向锁。2、轻量级锁引入轻量级锁的目的是为了减少传统重量级锁使用操作系统MutexLock(互斥锁)的性能消耗,无需多线程竞争。如果使用MutexLock,每次获取和释放锁的操作都会带来用户态和内核态的切换,会造成较大的系统性能开销。当关闭偏向锁或多个线程竞争偏向锁时,偏向锁将升级为轻量级锁。轻量级锁的获取和释放是通过CAS完成的,通过一定的自旋次数可以实现锁的获取。结束。需要强调的注意事项:不能用轻量级锁代替重量级锁。其初衷是在没有多线程竞争的情况下,降低传统重量级锁的性能消耗。轻量级锁适用的场景是线程交替执行同步块的情况。如果多个线程同时访问,会导致轻量级锁扩展为重量级锁。3.重量级锁synchronized依赖monitor实现方法同步或者代码块同步。代码块同步是使用monitorenter和monitorexit指令实现的。monitorenter指令在编译后插入到同步代码块的开头,monitorexit插入在方法的末尾和异常处。任何对象都有与之关联的监视器。按住Monitor时,它将处于锁定状态。比如下面的加锁代码:publicclassSynchronizedToMonitorExample{publicstaticvoidmain(String[]args){intcount=0;synchronized(SynchronizedToMonitorExample.class){for(inti=0;i<10;i++){count++;}}System.out.println(count);}}当我们将上面的代码编译成字节码时,其内容如下:从上面的结果可以看出,在main方法的执行中有多个monitorenter和monitorexit指令,所以可以看出synchronized是依靠Monitor监视器锁实现的,而监视器锁依赖于操作系统的互斥锁(MutexLock)。互斥锁每次获取和释放锁,都会带来用户态和内核态的切换。它增加了系统的性能开销。总结Synchronized在JDK1.6中优化了它的性能。在一系列优化方法中,锁扩展是提高synchronized执行效率的关键手段之一。重量级锁,最后是重量级锁的过程。重量级之前的所有状态在大多数情况下都可以大大提高synchronized的性能。