synchronized在JDK1.5之前性能比较低,那时候我们通常选择使用Lock而不是synchronized。然而,这种情况在JDK1.6中得到了改变。在JDK1.6中,对synchronized做了各种优化,性能也有了很大的提升。这也是当前版本中经常看到synchronized的重要原因之一。当然,除了性能,synchronized的使用也很方便,这也是它流行的一个重要原因。在众多的优化方案中,锁扩展机制是提升synchronized性能最有利的手段之一(其他优化方案我们后面会讲到)。在这篇文章中,我们关注什么是锁扩展?以及锁扩展的各种细节。当文本在JDK1.5时,synchronized需要调用监听锁(Monitor)来实现。监听锁本质上是依赖底层操作系统的MutexLock(互斥锁)实现的。互斥锁释放和获取时,需要从用户态切换到内核态,成本高,执行时间长。这种依赖于操作系统MutexLock实现的锁被称为“重量级锁”。.什么是用户态和内核态?用户态:当一个进程正在执行用户自己的代码时,称它处于用户运行态。内核态:当一个任务(进程)执行系统调用,落入内核代码的执行中,我们调用内核运行态的进程。此时,处理器以最高特权级在内核代码中执行。为什么要分开内核态和用户态?假设不区分内核态和用户态,程序可以随意读写硬件资源,比如随意读写和分配内存。这样,如果程序员不小心把不合适的内容写到不该写的地方,很可能会导致系统崩溃。有了用户态和内核态的区分,程序在执行某个操作时会进行一系列的验证和检查。在破坏系统的情况下,即有了内核态和用户态的区分,程序可以更安全地运行,但同时在两种模式之间切换会造成一定的性能开销。锁扩展在JDK1.6中,为了解决获取和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”两种状态。此时一共有四种synchronized状态:无锁偏向锁、轻量级锁、重量级锁的等级依次升级。我们称这个升级过程为“锁扩展”。PS:到现在锁的升级都是单向的,也就是说只能从低到高升级(无锁->偏向锁->轻量级锁->重量级锁),不会有锁降级条件。为什么锁扩展可以优化synchronized的性能?当我们了解了这些锁状态之后,自然就有了答案。让我们一起来看看吧。1.偏向锁HotSpot笔者通过研究和实践发现,在大多数情况下,并没有多线程竞争锁,总是同一个线程多次获取锁。为了让线程获取锁的代价更低,偏向Lock。偏向锁意味着它会偏向于第一个访问锁的线程。如果运行时只有一个线程访问同步锁,没有多线程争用,则不需要该线程。同步被触发。在这种情况下,将向线程添加偏向锁。偏向锁执行流程当线程访问同步代码块并获得锁时,会将偏向锁线程ID存储在对象头的MarkWord中,线程进入时不再通过CAS操作加锁解锁并退出同步块,而是检测MarkWord中是否存在指向当前线程的偏向锁。如果MarkWord中的线程ID与访问的线程ID相同,则可以直接进入同步块执行代码。如果线程ID不同,则使用CAS尝试获取锁,获取成功则进入同步块执行代码,否则锁的状态将升??级为轻量级锁。偏向锁的优点偏向锁的设计目的是在没有多线程竞争的情况下,尽量减少不必要的锁切换,因为锁的获取和释放依赖于多条CAS原子指令,而偏向锁只需要在替换时执行一次CAS原子指令指定线程ID。MarkWord扩展知识:内存布局在HotSpot虚拟机中,对象在内存中的布局可以分为以下三个区域:对象头(Header)、实例数据(InstanceData)、对齐填充(Padding),以及对象头包含A: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;同步(SynchronizedToMonitorExample.class){for(inti=0;i<10;i++){count++;}}System.out.println(计数);}}当我们将上面的代码编译成字节码后,其内容如下:从上面的结果可以看出,在main方法的执行中有多个monitorenter和monitorexit指令,可以看出synchronized是通过Monitor依赖于监视器锁,而监视器锁依赖于操作系统的互斥锁(MutexLock)。mutex每次获取和释放锁都会带来用户态和内核态的切换,增加了系统的性能开销。总结Synchronized在JDK1.6中优化了它的性能。在一系列优化方法中,锁扩展是提高synchronized执行效率的关键手段之一。重量级锁,最后是重量级锁的过程。重量级之前的所有状态在大多数情况下都可以大大提高synchronized的性能。本系列推荐文章与并发第一课:Thread解释了Java中用户线程和守护线程的区别那么多?深入理解ThreadPool线程池的7种创建方式,强烈推荐大家使用……池化技术到底有多牛?看到线程和线程池的对比,惊呆了!并发中的线程同步和locksynchronized锁this和class的区别!volatile和synchronized的区别轻量级锁就一定比重量级锁快吗?这样终止线程会导致服务宕机?SimpleDateFormat线程不安全的5个解决方案!ThreadLocal不好用?这就是你没用的原因!ThreadLocal内存溢出代码演示及原因分析!信号量告白:限流器我用对了!CountDownLatch:别招手了,等大家再次加入我们!CyclicBarrier:当所有人都准备好后,司机就可以发动汽车了!关注公众号“Java中文社区”,查看更多有趣的Java并发知识文章。
