广州建设局官方网站,网络品牌营销案例,全国商城网站建设,平泉县住房和城乡建设局网站作者 | 王磊来源 | Java中文社群#xff08;ID#xff1a;javacn666#xff09;转载请联系授权#xff08;微信ID#xff1a;GG_Stone#xff09;synchronized 在 JDK 1.5 之前性能是比较低的#xff0c;在那时我们通常会选择使用 Lock 来替代 synchronized。然而这个情… 作者 | 王磊来源 | Java中文社群IDjavacn666转载请联系授权微信IDGG_Stonesynchronized 在 JDK 1.5 之前性能是比较低的在那时我们通常会选择使用 Lock 来替代 synchronized。然而这个情况在 JDK 1.6 时就发生了改变JDK 1.6 中对 synchronized 进行了各种优化性能也得到了大幅的提升这也是目前版本中还能经常见到 synchronized 身影的重要原因之一。当然除了性能之外synchronized 的使用也非常便利这也是它流行的重要原因。在众多优化方案中锁膨胀机制是提升 synchronized 性能最有利的手段之一其他优化方案我们后面再讲本文我们重点来看什么是锁膨胀以及锁膨胀的各种细节。正文在 JDK 1.5 时synchronized 需要调用监视器锁Monitor来实现监视器锁本质上又是依赖于底层的操作系统的 Mutex Lock互斥锁实现的互斥锁在进行释放和获取的时候需要从用户态转换到内核态这样就造成了很高的成本也需要较长的执行时间这种依赖于操作系统 Mutex Lock 实现的锁我们称之为“重量级锁”。什么是用户态和内核态用户态User Mode当进程在执行用户自己的代码时则称其处于用户运行态。内核态Kernel Mode当一个任务进程执行系统调用而陷入内核代码中执行时我们就称进程处于内核运行态此时处理器处于特权级最高的内核代码中执行。为什么分内核态和用户态假设没有内核态和用户态之分程序就可以随意读写硬件资源了比如随意读写和分配内存这样如果程序员一不小心将不适当的内容写到了不该写的地方很可能就会导致系统崩溃。而有了用户态和内核态的区分之后程序在执行某个操作时会进行一系列的验证和检验之后确认没问题之后才可以正常的操作资源这样就不会担心一不小心就把系统搞坏的情况了也就是有了内核态和用户态的区分之后可以让程序更加安全的运行但同时两种形态的切换会导致一定的性能开销。锁膨胀在 JDK 1.6 时为了解决获取锁和释放锁带来的性能消耗引入了“偏向锁”和“轻量级锁”的状态此时 synchronized 的状态总共有以下 4 种无锁偏向锁轻量级锁重量级锁锁的级别按照上述先后顺序依次升级我们把这个升级的过程称之为“锁膨胀”。“PS到现在为止锁的升级是单向的也就是说只能从低到高升级无锁 - 偏向锁 - 轻量锁锁 - 重量级锁不会出现锁降级的情况。锁膨胀为什么能优化 synchronized 的性能当我们了解了这些锁状态之后自然就会有答案下面我们一起来看。1.偏向锁HotSpot 作者经过研究实践发现在大多数情况下锁不存在多线程竞争总是由同一线程多次获得的为了让线程获得锁的代价更低于是就引进了偏向锁。偏向锁Biased Locking指的是它会偏向于第一个访问锁的线程如果在运行过程中同步锁只有一个线程访问不存在多线程争用的情况则线程是不需要触发同步的这种情况下会给线程加一个偏向锁。偏向锁执行流程当一个线程访问同步代码块并获取锁时会在对象头的 Mark Word 里存储锁偏向的线程 ID在线程进入和退出同步块时不再通过 CAS 操作来加锁和解锁而是检测 Mark Word 里是否存储着指向当前线程的偏向锁如果 Mark Word 中的线程 ID 和访问的线程 ID 一致则可以直接进入同步块进行代码执行如果线程 ID 不同则使用 CAS 尝试获取锁如果获取成功则进入同步块执行代码否则会将锁的状态升级为轻量级锁。偏向锁的优点偏向锁是为了在无多线程竞争的情况下尽量减少不必要的锁切换而设计的因为锁的获取及释放要依赖多次 CAS 原子指令而偏向锁只需要在置换线程 ID 的时候执行一次 CAS 原子指令即可。Mark Word 扩展知识内存布局在 HotSpot 虚拟机中对象在内存中存储的布局可以分为以下 3 个区域对象头Header实例数据Instance Data对齐填充Padding对象头中又包含了Mark Word标记字段我们的偏向锁信息就是存储在此区域的。Klass PointerClass 对象指针对象在内存中的布局如下在 JDK 1.6 中默认是开启偏向锁的可以通过“-XX:-UseBiasedLockingfalse”命令来禁用偏向锁。2.轻量级锁引入轻量级锁的目的是在没有多线程竞争的前提下减少传统的重量级锁使用操作系统 Mutex Lock互斥锁产生的性能消耗。如果使用 Mutex Lock 每次获取锁和释放锁的操作都会带来用户态和内核态的切换这样系统的性能开销是很大的。当关闭偏向锁或者多个线程竞争偏向锁时就会导致偏向锁升级为轻量级锁轻量级锁的获取和释放都通过 CAS 完成的其中锁获取可能会通过一定次数的自旋来完成。注意事项需要强调一点轻量级锁并不是用来代替重量级锁的它的本意是在没有多线程竞争的前提下减少传统的重量级锁使用产生的性能消耗。轻量级锁所适应的场景是线程交替执行同步块的情况如果同一时间多个线程同时访问时就会导致轻量级锁膨胀为重量级锁。3.重量级锁synchronized 是依赖监视器 Monitor 实现方法同步或代码块同步的代码块同步使用的是 monitorenter 和 monitorexit 指令来实现的monitorenter 指令是在编译后插入到同步代码块的开始位置而 monitorexit 是插入到方法结束处和异常处的任何对象都有一个 Monitor 与之关联当且一个 Monitor 被持有后它将处于锁定状态。如以下加锁代码public class SynchronizedToMonitorExample {public static void main(String[] args) {int count  0;synchronized (SynchronizedToMonitorExample.class) {for (int i  0; i  10; i) {count;}}System.out.println(count);}
}
当我们将上述代码编译成字节码之后它的内容是这样的从上述结果可以看出在 main 方法的执行中多个 monitorenter 和 monitorexit 的指令由此可知 synchronized 是依赖 Monitor 监视器锁实现的而监视器锁又是依赖操作系统的互斥锁Mutex Lock互斥锁在每次获取和释放锁时都会带来用户态和内核态的切换这样就增加了系统的性能开销。总结synchronized 在 JDK 1.6 时优化了其性能在一系列优化的手段中锁膨胀是提升 synchronized 执行效率的关键手段之一锁膨胀指的是 synchronized 会从无锁状态、到偏向锁、到轻量级锁最后到重量级锁的过程。重量级之前的所有状态在绝大数情况下可以大幅的提升 synchronized 的性能。本系列推荐文章1.线程的故事我的3位母亲成就了优秀的我2.线程池的7种创建方式强烈推荐你用它...3.轻量级锁一定比重量级锁快吗4.这样终止线程竟然会导致服务宕机5.漫画如何证明sleep不释放锁而wait释放锁6.池化技术到达有多牛看了线程和线程池的对比吓我一跳7.求求你别再用wait和notify了8.Semaphore自白限流器用我就对了9.CountDownLatch别浪等人齐再团10.CyclicBarrier人齐了老司机就发车了11.Java中用户线程和守护线程区别这么大12.ThreadLocal不好用那是你没用对13.ThreadLocal内存溢出代码演示和原因分析14.SimpleDateFormat线程不安全的5种解决方案15.synchronized 加锁 this 和 class 的区别
往期推荐
synchronized 加锁 this 和 class 的区别使用了synchronized竟然还有线程安全问题图解Java中的18 把锁