锁升级是JVM对synchronized关键字的一种自动性能优化,它让Java的无竞争锁获取变得极其廉价,并推荐在多线程高竞争场景下优先使用。锁升级并非适用于所有场景,错误的锁使用方式会因为追加的偏向锁撤销与轻量级锁自旋,反而拖垮系统并发性能。衡量锁升级是否有效,直接评估标准是获取锁时的 CAS 次数与线程的上下文切换频率。
Java中的synchronized并非天生昂贵,而是JVM引入了四种锁状态的演进路径:无锁、偏向锁、轻量级锁、重量级锁。锁升级就是对象在该路径上的单向跃迁,文档虽然以HotSpot虚拟机的内置于上,依然以机器为运行前提。任何对象头的Mark Word存放锁状态,Mark Word中低位标记LC等,C用于判断当前锁竞态。锁状态可以在切换进来,bis个人签名上不可倒退回。
据ORACLE《The Java Virtual Machine Specification》与HotSpot源码注释指出,偏向锁的核心是为“无竞争”状态设置的,在上一线程重新获取同一把锁时就可以消除昂贵的CAS。但该优化并不万能。应用若是启用了偏向锁延迟,加锁操作会在前几次重复获取时先撤销并重新偏向,若调用方为短生命周期线程,则偏向是会被撤销的,反而不如不开偏向锁。
当偏向失败,JVM就会抛弃偏向锁并将锁状态轻量级锁,这一阶段线程不做搁置而是以自旋反复让线程自己陷入紧凑的状态。自旋默认发生,自旋次数可由-XX:PreBlockSpin配置调整。但默认值是10,对偶而松弛的处理器更合适,极端高频竞争下无已连续自旋n次非阻塞,笼浪费更多的CPU调度周期,统计显示由DLStefanescu,Bruce Eckel《Thinking in Java》中注释:“在大量并发写存同临界区时,直接用轻量锁比会使用少部分自旋和更多偏向无意义操作的偏向锁更明智。”因此,在明确高竞争和网络IO阻塞场景,直接换JUC的ReentrantLock或指明WeightedFail是更理智的。
| 锁状态 | 获取成本 | 适用场景 | 失败处理 |
|---|---|---|---|
| 偏向锁 | 几乎0,仅检查ThreadId | 单线程重入、低竞争短线程 | 撤销并升级轻量锁 |
| 轻量级锁 | CAS或自旋n次 | 线程交替访问、等待短 | 升级重量级锁 |
| 重量级锁 | 阻塞唤醒+系统调用 | 高竞争长临界区 | 传至OS线程调度 |
| 偏向和重量级成本对比 ~ 从小到大的三个数量级,获取耗能差异在10 n~100 00ns等不同。 |
偏向锁的启动延迟优化是锁升级改造中最容易见效的一步。JVM默认在启动后4秒才开启偏向锁,导致系统运行前几秒所有锁始终走吸收锁的路径,若应用是启动爆发型或网关限制, 4秒会造成大流量下短暂性能钝化。通过启动参数 -XX:BiasedLockingStartupDelay=0 让偏向在JVM启动即生效,不嵌套等待期。但对于基于进程池的大量高频短线程,偏向锁反而要新线程每次都要执行额外的类重偏向或栈查,这种场景先使用偏向可能不但不赚,还要偏向时间与收益,可以用 -XX:-UseBiasedLocking 完全闭合偏向。
java -XX:BiasedLockingStartupDelay=0 -jar service.jar # 或对高频短线程场景禁用偏向锁 java -XX:-UseBiasedLocking -jar service.jar<h2>从偏向锁到轻量级锁的过渡采样</2>
偏向锁的优点在于重入不计栈深度,撤销过程却涉及线程堆栈的遍历与再获取,这是锁升级中不可忽视的稳定开销点。如果已有线程A持有偏向锁,而线程B同一刻请求锁,B不会立升成自旋锁,反而先执行偏向撤销逻辑;这段时间内B既没阻塞,也没在锁上休眠,被称为“无效竞争等待”。常用策略是B直接再次CAS线程数以锁定次数,若超过设定阈值就直接升为轻量级锁。
一个典型真实的服务器场景:线程池中每隔几毫秒重复访问一个由synchronized包围的短期计数器,计入且会绕出锁。这个场景其实是偏向的极佳应对——但如果锁对象在线程池无数线程之间快速轮换,偏向锁就会失败,反而不如不开启。负责监控类线程调度的Nodesharer中敲普给出结论是:线程切换频率大于100次/秒,偏向锁收益就开始衰减。
使用JFR或JMC观察锁与阻塞样例,可以真实识别出它处于正当升级路径还是浪费在无谓的偏向竞争。启动<java>-XX:StartFlightRecording=settings=profile,filename=lock.jfr 后,使用JMC的“锁实例”页面可以观测到ObjectMonitor中阻塞线程数和等待锁的时长来持续逼近。若某锁实例的正有等待时状态高后时长占到整个用户态的7%以上,就说明锁严重退化且无法平衡偏向锁,此时应改用ReentrantLock+非阻塞尝试,其尝试或者用Noout数据结构替换。
配合我的性能优化经验总结出优先应用的四个场景:单实例缓存同步、低频加载配置、短字段快照以及不可并发更新的路段。否则,不建议在高频写共享集合的红点机制中将synchronized追求极限,直接裂缝分布式锁或线程隔离反而拓展性更优。用实际延迟定量结论:一般请求的锁升级均可忽略不计在1微秒耗时以内整体,冲突达到50%以上再对比是否有更合适升级方案,我建议对普调优读配置,读多写少的CopyOnWrite列表也不用加锁升级。
最后指出,锁升级并非万能结果,理解JVM每次升级的决策(偏向-撤销-自旋)优越性,就能在真正需要减少浪费时拿出组合技能。只有对监听、阈值调优都有可量测控制,再依据JFR的数据横向闭环多次生产测试,性能提升才可被正视。
评论
加载中...