Java多线程基础-15:Java 中 synchronized 的优化操作 -- 锁升级、锁消除、锁粗化

Java锁升级的详细介绍 《Java锁机制详解:从偏向锁到重量级锁》 本文系统介绍了Java中的三种锁机制及其升级过程。偏向锁基于"无竞争"假设,通过标记线程ID减少同步开销,适用于单线程重复访问场景;轻量级锁采用CAS操作避免阻塞,适合线程交替执行的轻度竞争环境;重量级锁依赖操作系统互斥量实现严格同步,能处理高并发竞争但性能较低。JVM会根据竞争强度自动升级锁机制(01→00→10),开发者可通过-XX参数调优。随着JDK版本迭代,偏向锁已被标记废弃,推荐使用基于CAS的并发工具。合理选择锁机制对系统性能至关重 阅读详情

由 并发编程中常见的锁策略 总结可知,synchronized 具有以下几个特性:

  1. 开始时是乐观锁,如果锁冲突频繁,就转换为悲观锁。
  2. 开始是轻量级锁实现,如果锁被持有的时间较长,就转换成重量级锁。
  3. 实现轻量级锁时,大概率用自旋锁策略。
  4. 是一种不公平锁。
  5. 是一种可重入锁。
  6. 不是读写锁。

本文介绍synchronized的几种优化操作,包括锁升级、锁消除和锁粗化。

一、锁升级

JVM 将 synchronized 锁分为无锁、偏向锁、轻量级锁、重量级锁这四种状态。在加锁过程中,会根据实际情况,依次进行升级。(**目前主流的 JVM 的实现,只能锁升级,不能锁降级!**不是无法实现,只不过可能是因为存在一些代价,使得这样做的收益和代价不成比例,因此就没有实现。)

整体的加锁过程(锁升级过程):刚开始加锁,是偏向锁状态;遇到锁竞争后,升级成自旋锁(轻量级锁);当竞争更激烈时,就会变成重量级锁(交给内核阻塞等待)。

1、偏向锁(Biased Locking)

第一个尝试加锁的线程优先进入偏向锁状态。偏向锁是Java虚拟机(JVM)中用于提高线程同步性能的一种优化技术。在多线程环境中,对共享资源进行同步操作,需要使用锁(synchronized)来保证线程的互斥访问。传统的锁机制存在竞争和上下文切换的开销,对性能会有一定的影响。而偏向锁则是为了减少无竞争情况下的锁操作开销而引入的。

偏向锁不是真的“加锁”,只是先让线程针对锁对象有个标记,记录某个锁属于哪个线程。

它的基本思想是,当一个线程获取锁并访问同步代码块时,如果没有竞争,那么下次该线程再次进入同步块时,无需再次获取锁。这是因为在无竞争的情况下,假设一个线程反复访问同步代码块,无需每次都去竞争锁,只需判断锁是否处于偏向状态;如果是,那么直接进入同步代码块即可。

通俗来说就是,如果后续没有其他线程再来竞争该锁,那么就不用真的加锁了,从而避免了加锁解锁的开销。 但一旦还有其他线程来尝试竞争这个锁,偏向锁就立即升级成真的锁(轻量级锁),此时别的线程就只能等待了。这样做既保证了效率,也保证了线程安全。

如何判定有没有别的线程来竞争该锁?

注意,偏向锁是synchronized内部做的工作。synchronized会针对某个对象进行加锁,这个所谓的“偏向锁”正是在这个对象里头做一个标记。

由于一开始已经在锁对象中记录了当前锁属于哪个线程,因此很容易识别当前申请锁的线程是否是一开始就记录了的线程。

如果另一个线程正在尝试对同一个对象进行加锁,也会先尝试做标记,但结果却发现已经有标记了。于是JVM就会通知先来的线程,让它赶快把锁升级一下。

偏向锁本质上是“延迟加锁”,即能不加锁就不加锁,尽量避免不必要的加锁开销;但是该做的标记还是得做的,否则就无法区分何时需要真正加锁。

举个栗子理解偏向锁

假设男主是一个锁,女主是一个线程。如果只有女主和男主暧昧(即只有这一个线程来使用这个锁),那么即使男主和女主不领证结婚(避免了高成本操作),也可以一直生活下去。

但是如果此时有女配出现,也尝试竞争男主,想和男主搞暧昧,那么此时女主就必须当机立断,不管领证结婚这个操作成本多高,也势必要把这个动作完成(即真正加锁),让女配死心。

所以说,偏向锁 = 搞暧昧~~

2、自旋锁

**什么是自旋锁?**在锁策略的文章中提到:

自旋锁是一种典型的轻量级锁的实现方式,它通常是纯用户态的,不需要经过内核态。按之前的方式,线程在抢锁失败后即进入阻塞状态,放弃 CPU,需要过很久才能再次被调度。但实际上,在大部分情况下虽然当前抢锁失败,但过不了很久锁就会被释放,没必要就放弃 CPU。这个时候就可以使用自旋锁来处理这样的问题。

自旋锁是一种忙等待锁的机制。当一个线程需要获取自旋锁时,它会反复地检查锁是否可用,而不是立即被阻塞。如果获取锁失败(锁已经被其他线程占用),当前线程会立即再尝试获取锁,不断自旋(空转)等待锁的释放,直到获取到锁为止。第一次获取锁失败,第二次的尝试会在极短的时间内到来。这样能保证一旦锁被其他线程释放,当前线程能第一时间获取到锁。

优点:没有放弃 CPU,不涉及线程阻塞和调度。一旦锁被释放就能第一时间获取到锁。
缺点:如果锁被其他线程持有的时间比较久,那么就会持续的消耗 CPU 资源(忙等),而挂起等待的时候是不消耗 CPU 的。

自旋锁适用于保护临界区较小、锁占用时间短的情况,因为自旋会消耗CPU资源。自旋锁通常使用原子操作或特殊的硬件指令来实现。

随着其他线程进入锁竞争,偏向锁状态会被消除,进入轻量级锁状态,即自适应的自旋锁。

此处的轻量级锁是通过 CAS 来实现。通过 CAS 检查并更新一块内存 (比如比较 null 与该线程引用是否相等),如果更新成功,则认为加锁成功;如果更新失败,则认为锁被占用,继续自旋式的等待,期间并不放弃 CPU 资源。

(见 详解CAS算法)

CAS算法实现自旋锁的原理

由于自旋操作是一直让 CPU 空转,比较浪费 CPU 资源,因此此处的自旋不会一直持续进行,而是达到一定的时间或重试次数就不再自旋了。这也就是所谓的 “自适应”。

3、重量级锁

**什么是重量级锁 ?**在锁策略的文章中提到:

简单来说,轻量级锁是加锁解锁的过程更快更高效的锁策略,而重量级锁是加锁解锁的过程更慢更低效的锁策略。重量级锁中加锁机制重度依赖 OS 提供的 mutex(互斥量)。

  • 大量的内核态用户态切换。
  • 很容易引发线程的调度。

这两个操作的成本都比较高,而且一旦涉及到用户态和内核态的切换,效率就低了。

如果竞争进一步激烈,自旋不能快速获取到锁状态。就会膨胀为重量级锁。

自旋锁虽然能最快获取到锁,但是要消耗大量 CPU(因为自旋的时候CPU是快速空转的)。如果当前锁竞争非常激烈,比如 50 个线程竞争一个锁,1 个争上,另外 49 个等待。这么多线程都在自旋空转,CPU的消耗就非常大。既然如此,就更改锁策略,升级成重量级锁,让其它的线程都在内核里进行阻塞等待(这意味着线程要暂时放弃 CPU 资源,由内核进行后续调度)。

(PS:目前的主流操作系统如 windows,Linux,调度的开销都是很大的。系统不承诺能在 xx 时间内一定能完成指定的调度,极端情况下调度的开销可能非常大。

但还存在另外一种实时操作系统(例如 vxworks),它能够以更低的成本完成任务调度,但牺牲了更多的其他功能。在如火箭发射这种对时间精度比较高的特殊领域就会用到。)

如果竞争进一步激烈,自旋不能快速获取到锁状态。就会膨胀为重量级锁。

此处的重量级锁就是指内核提供的 mutex 。

  1. 某线程执行加锁操作,先进入内核态。
  2. 在内核态判定当前锁是否已经被别的线程占用 。
  3. 如果该锁没有占用,则加锁成功,并切换回用户态。
  4. 如果该锁被占用,则加锁失败。此时线程进入锁的等待队列并挂起,等待被操作系统唤醒。
  5. 经历了一系列的“沧海桑田”,这个锁终于被其他线程释放了,此时操作系统也想起了这个被挂起的线程,于是唤醒这个线程,并让它尝试重新获取锁。

二、锁消除

锁消除也是“非必要,不加锁”的一种体现。与锁升级不同,锁升级是程序在运行阶段 JVM 做出的优化手段。而锁消除是在程序编译阶段的优化手段。编译器和 JVM 会检测当前代码是否是多线程执行或是否有必要加锁。如果无必要,但又把锁给写了,那么在编译的过程中就会自动把锁去掉。

有些应用程序代码中可能会用到没有必要用到的 synchronized。例如 StringBuffer 就是线程安全的,它的每一个关键方法都加了synchronized关键字:

StringBuffer的部分源码

但这里就有一个问题:如果是在单线程中使用StringBuffer,是不涉及线程安全问题的。这个时候其实就没必要加锁。那么这时编译器就会出手,发现synchronized是没必要加的,就会在编译阶段把synchronized去掉,相当于加锁操作没有真正被编译。

StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");

此时,每个 append 的调用都会涉及加锁和解锁。但如果只是在单线程中执行这段代码,那么其中的这些加锁解锁操作是没有必要的,白白浪费了一些资源开销。

锁消除整体来说是一个比较保守的优化手段,毕竟编译器肯定得保证消除的操作是靠谱的。所以只有十拿九稳的时候才会实施锁消除,否则仍然会上锁,这时就会交给其它的操作策略来对锁进行优化(比如上面的锁升级)。

三、锁粗化

锁的粒度指的是 synchronized 代码块中包含代码的多少。代码越多,粒度越大;代码越少,粒度越小。

一般我们在写代码时,多数情况下是希望锁的粒度更小一点。(锁的粒度小就意味着串行执行的代码更少,并发执行的代码更多)。如果某个场景需要频繁地加锁解锁,此时编译器就可能把这个操作优化成个粒度更粗的锁,即锁的粗化。

实际开发过程中使用细粒度锁,是期望释放锁的时候其他线程能使用锁。但是实际中可能并没有其他线程来抢占这个锁。这种情况 JVM 就会自动把锁粗化,避免频繁申请释放锁造成不必要的开销。

举个栗子理解锁粗化

上班时要向领导汇报工作。你的领导给你安排了三个工作:A、B、C。
汇报方式有:

  1. 先打个电话,汇报工作 A 的进展,挂了电话;再打个电话,汇报工作 B 的进展,挂了电话;再打个电话,汇报工作C的进展,挂了电话。(你给领导打电话,领导接你的电话,领导就干不了别的;别人要给领导打电话,就只能阻塞等待。每次锁竞争都可能引入一定的等待开销,此时整体的效率可能反而更低。)
  2. 打个电话,一口气汇报 工作 A,工作B,工作 C,挂了电话。

显然第二种方式是更加高效的。

可见,synchronized 的策略是比较复杂的,它是一个很“智能”的锁。

Java中synchronized的优化 本文介绍为了实现高效并发,虚拟机对 synchronized 做的一系列的锁优化措施。 阅读详情

相关推荐

锁进阶 -- synchronized 的锁优化

介绍 synchronized 锁的优化,包括锁升级、锁消除、锁粗化。

想听风雨的博客 940

java锁升级

我们简称为。

weixin_40482816的博客 4482

【多线程】synchronized底层实现的方式

在java 开发中对于锁的应用非常的常见,如果对于什么时候该用什么锁,以及锁实现的原理有所不知道的,或者面试过程中面试官问你不知道怎么回答的,欢迎来看下面的文章。

李光 未来科技 1007

Java中的锁升级

关于锁升级的四种级别,即如何升级

热爱编程的菜鸟 9820

深度剖析Java Synchronized 的锁机制,基于Java 15废弃偏向锁讲解!

Java 15 废弃偏向锁 JDK 15已经在2020年9月15日发布,详情见 JDK 15 官方计划。其中有一项更新是废弃偏向锁,官方的详细说明在:JEP 374: Disable and Deprecate Biased Locking。 当时为什么要引入偏向锁? 偏向锁是 HotSpot 虚拟机使用的一项优化技术,能够减少无竞争锁定时的开销。偏向锁的目的是假定 monitor 一直由某个特定线程持有,直到另一个线程尝试获取它,这样就可以避免获取 monitor 时执行 cas 的原子操作。monito

weixin_49494194的博客 572

用ESP32-S3+OV2640摄像头+ILI9488屏,DIY一个能联网的智能猫眼(附完整代码)

本文详细介绍了如何利用ESP32-S3微控制器搭配OV2640摄像头和ILI9488显示屏,DIY一个功能强大的智能猫眼系统。从硬件选型、电路设计到软件开发,提供了完整的解决方案和优化技巧,包括图像采集显示、WiFi远程访问及低功耗设计,适合智能家居爱好者动手实践。

weixin_30897233的博客 501

【多线程】synchronized原理

synchronized的原理,锁升级、锁消除、锁粗化

Yeeear的博客 2370

深入解析以太网接口中的BOB Smith电路设计

本文深入解析以太网接口中的BOB Smith电路设计,详细介绍了其在共模噪声抑制、浪涌防护和EMI优化中的关键作用。通过实际案例和设计要点,帮助工程师正确选择75Ω电阻和高压电容等元件,优化PCB布局,解决电压型与电流型PHY的设计差异,以及千兆与百兆以太网的特殊考量。

weixin_28339967的博客 684

Java多线程进阶-深入synchronized与CAS

本篇笔记深入到更底层的实现,去探寻synchronized的锁升级机制和无锁编程CACAS (Compare-and-Swap) 是实现“乐观锁”的原子操作,也是JUC(`java.util.concurrent`)包中许多高性能类的幕后英雄。而 `synchronized`,这个我们最熟悉的关键字,其内部为了追求极致性能而进行的优化和演进,恰恰就是对CAS等思想的应用。可以说,理解了它们,我们才能真正看懂Java并发性能优化的精髓。

weixin_73966418的博客 1331

3GPP LTE/NR信道模型

CDL(Clustered Delay Line):簇延迟线模型,包括CDL-A、CDL-B、CDL-C三个用于NLOS的信道模型,以及CDL-D、CDL-E两个用于LOS的信道模型。

LinkEverything的博客 1万+

从理论到实践:利用FilterSolutions高效设计7阶椭圆低通滤波器

本文详细介绍了如何利用FilterSolutions软件高效设计7阶椭圆低通滤波器。文章从理论入手,对比了巴特沃斯、切比雪夫等经典滤波器特性,阐述了在要求高衰减陡度(如62dB抑制)和严格通带波纹(0.2dB)时,椭圆滤波器是最优选择。通过分步实战教程,指导读者完成从软件参数设置、性能验证到实际电路布局与调试的全过程,助力射频与高速数字信号处理工程师将理论转化为可靠设计。

jupi8的博客 558

JUC:Synchronized和锁升级

1. 面试题 2. Synchronized的性能变化 3. synchronized锁种类和升级步骤   3.1 多线程访问情况   3.2 升级流程   3.3 无锁   3.4 偏向锁   3.5 轻量锁   3.5 重量锁   3.6 小结 4. JIT编译器对锁的优化   4.1 锁消除   4.2 锁粗化

2301_79526467的博客 1268

如何解除Uniaccess管控??

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值 。

**My Coding Family** 2380

12 Synchronized与锁升级

12 Synchronized与锁升级

学习 | 共享 | 前进 4156

我的世界模组开发——前言

避坑:在开发过程中一定不要过分依赖AI,虽说现在的人工智能很厉害,但是也有很大的弊端,很多AI生成的代码错误百出,还得自己修改,而且像deepseek这种AI的训练数据还停留在一年前,给出的maven仓库地址都是过时的甚至是错误的,开发模组就是要多学习,多看源码,这样开发模组的水平才能越来越高。总而言之,build.gradle文件在我的世界Forge模组开发中扮演了重要的角色,它定义了项目的构建过程、依赖关系和编译选项,为开发者提供了灵活而强大的构建工具。开发工具推荐使用idea,因为可以使用通灵义码。

qq_24992377的博客 2593

STM32(HAL库)驱动OLED

通过STM32F103C8T6单片机采用HAL库方式对0.96寸OLED(IIC通讯)屏幕进行驱动。

weixin_44597885的博客 8789

synchronized锁升级流程

hashCode去哪了?计算过hashCode后和偏向锁状态是不共存的。如果可以共存,那hashCode必定会被偏向的线程ID所覆盖,那么就会造成一个对象前后两次调用hashCode得不到相同的结果轻量级锁:JVM会在当前线程的栈帧中创建一个锁记录(Lock Record)空间,用于存储对象的MarkWord拷贝。释放锁后又会写回对象头。虽然前62位用于记录指向堆中Monitor对象的指针,但具体来说,ObjectMonitor类中的字段_header用于记录非加锁状态下的MarkWord。

u014474768的博客 1371

补丁1.0:使用Eviews做ADF检验

补丁1.0:使用Eviews做ADF检验

Jet4505的博客 1961

Java锁升级机制深度解析:从偏向锁到重量级锁的完整演进之路

Java的锁升级机制是JVM并发优化艺术的杰出体现。它通过四级锁状态的无缝切换,在安全与性能之间找到了精妙的平衡点。理解这一机制不仅有助于编写高性能的并发代码,更能让我们深入理解JVM的设计哲学。从无锁到重量级锁的每一步升级,都是对当前竞争状况的智能响应。虽然偏向锁在现代JDK中逐渐淡出,但锁升级的核心思想——根据实际情况动态调整同步策略——依然是我们设计并发系统时需要遵循的重要原则。

用文字记录技术,用分享回馈社区,用坚持见证成长 1222

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

15、JAVA中synchronized优化

在Java6之后自旋锁时自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就会少自旋甚至不自旋,总之,比较智能。另外JVM可能会做如下优化,把多次append的加锁操作粗化为一次(因为都是对同一个对象佳作,没必要重入多次)synchronized优化;

weixin_75149683的博客 1016

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

Synchronized与锁升级

Java juc synchronized

qq_57469718的博客 1108

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

由Java 15废弃偏向锁,谈谈Java Synchronized 的锁机制

Java 15 废弃偏向锁 JDK 15已经在2020年9月15日发布,详情见 JDK 15 官方计划。其中有一项更新是废弃偏向锁,官方的详细说明在:JEP 374: Disable and Deprecate Biased Locking。 当时为什么要引入偏向锁? 偏向锁是 HotSpot 虚拟机使用的一项优化技术,能够减少无竞争锁定时的开销。偏向锁的目的是假定 monitor 一直由某个特定线程持有,直到另一个线程尝试获取它,这样就可以避免获取 monitor 时执行 cas 的原子操作。monit.

罗翔 846

synchronized锁升级过程

​锁升级的完整过程:​​对象初始化后,处于无锁状态。当第一个线程访问时,JVM 会尝试为其加上偏向锁​(如果启用)。当第二个线程尝试获取锁时,​撤销偏向锁。两个线程开始通过 CAS 操作竞争轻量级锁。如果竞争轻微(如线程交替执行),轻量级锁自旋成功,则保持。如果竞争加剧(如自旋超过一定次数,或自旋线程数超过CPU核心数的一半),为了避免CPU空转,JVM 将轻量级锁升级为重量级锁。未获得锁的线程停止自旋,被挂起并进入阻塞队列。​锁的优缺点对比:​​锁状态优点缺点适用场景​。

只为成功找方法 不为失败找借口 592

【JUC】 Synchronized与锁升级

该文件主要介绍了Java中Synchronized的相关内容,包括其在Java 5以前是操作系统级别的重量级操作,Java 6后引入轻量级锁和偏向锁及它们的升级步骤、实现原理等。还提及了JIT编译器对锁的优化,如锁消除和锁粗化。此外,说明了各种锁的适用场景和优缺点,并指出Java 15后逐步废弃偏向锁的原因。

我只管埋头前行,剩下的交给时间。 1140

Java-JUC笔记-10 聊聊Synchronized叭

详细介绍了synchronized 的实现原理,锁的升级,使用场景等。

m0_56020664的博客 974

Synchronized锁升级(偏向锁、轻量锁、重量锁)

高并发时,同步调用应该考虑到锁的性能消耗。能用无锁的数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能使用对象锁,就不用类锁。

LX1232456的博客 1365
上一篇: Java多线程基础-14:并发编程中常见的锁策略
下一篇: Java多线程基础-16:简述Java并发编程JUC中的Callable接口
碳基肥宅
博客等级 码龄5年 1万+粉丝 119原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值