京东二面:Sychronized的锁升级过程是怎样的

引言

Java作为主流的面向对象编程语言,提供了丰富的并发工具来帮助开发者解决多线程环境下的数据一致性问题。其中,内置的关键字"Synchronized"扮演了至关重要的角色,它能够确保在同一时刻只有一个线程访问特定代码块或方法,从而有效地防止数据竞争和保持内存可见性。

在传统的Synchronized实现中,由于其采用的是重量级锁机制,每次获取和释放锁都涉及操作系统层面的线程调度,这无疑增加了线程上下文切换的开销,尤其在高并发且锁竞争较小的场景下,可能会导致不必要的性能损失。为此,从Java 6开始,JVM引入了锁升级机制,这是一种动态调整锁状态的技术,旨在根据不同场景灵活运用不同级别的锁,从而在保证并发安全性的同时,最大程度地提升程序的运行效率。

关于Synchronized的实现原理,请参考:美团一面:说说synchronized的实现原理?问麻了。。。。

本文将深入探讨"Synchronized"的锁升级过程,详细介绍从无锁状态到偏向锁、轻量级锁,直至重量级锁的不同阶段及其背后的原理。

Synchronized锁的基础概念

在Java中,synchronized关键字是实现线程同步的关键机制之一,它用于确保多个线程在访问共享资源时的正确性和一致性。synchronized锁的基本思想是,当一个线程进入某个synchronized代码块或方法时,它必须首先获取到该对象或类的锁,然后才能执行相应的操作。如果其他线程试图进入相同的synchronized区域,它们将被阻塞,直到锁被释放。

对象头与Mark Word简介

Java对象在内存中不仅包含类实例的字段,还包含一些元数据,这些元数据存储在对象头中。对象头是Java对象的重要组成部分,它包含了关于对象的重要信息,如哈希码、GC年龄以及锁状态等。其中,Mark Word是对象头中的一个关键字段,它记录了关于对象锁状态的信息。通过修改Mark Word的内容,JVM能够实现对对象锁的获取和释放。

Synchronized锁定的基本原理与运作机制概述

synchronized锁定的基本原理是通过对对象或类的监视器(Monitor)进行加锁和解锁操作来实现线程同步。当一个线程尝试进入synchronized代码块或方法时,它会首先尝试获取对象或类的锁。如果锁已经被其他线程持有,则该线程将被阻塞,直到锁被释放。synchronized锁的运作机制包括偏向锁、轻量级锁和重量级锁三种状态。偏向锁适用于单线程访问的情况,轻量级锁适用于多线程竞争不激烈的情况,而重量级锁则用于处理高竞争场景。通过这三种状态的转换,synchronized锁能够根据不同的并发场景动态调整锁策略,以实现高效的线程同步。

关于synchronized的实现方式,原理介绍,请参考:美团一面:说说synchronized的实现原理?问麻了。。。。

锁升级的概念

锁升级是指Java虚拟机(JVM)在并发环境下对synchronized关键字所使用的锁机制进行动态调整的过程,从最初的无锁状态逐渐过渡到偏向锁、轻量级锁,直至最终的重量级锁。这一过程旨在根据实际的并发状况选择最适合的锁类型,以实现对共享资源的最佳保护和最有效的并发控制。

锁升级的主要目的是为了提升并发性能,减少不必要的线程上下文切换和内存消耗。线程上下文切换是一个相对昂贵的操作,因为它涉及到保存当前线程的状态、恢复另一个线程的状态等一系列操作。通过优化锁策略,JVM可以减少这种切换的频率,从而提高系统的整体性能。

另外,锁升级也有助于减少内存消耗。相较于重量级锁需要创建额外的Monitor对象并在操作系统层面进行线程调度,偏向锁和轻量级锁在一定程度上降低了内存消耗,特别是对于大量短生命周期的锁请求场景。

Synchronized锁的四种状态详解

当我们使用synchronized时,Java虚拟机(JVM)会为每个被同步的对象维护一个锁(或称为监视器锁)。这个锁有四种状态:从级别由低到高依次是:无锁、偏向锁,轻量级锁,重量级锁,用于控制多线程对共享资源的访问。

image.png

无锁

无锁状态是对象初始化后的默认锁状态,表示对象当前未被任何线程锁定。在这种状态下,对象头的锁标志位通常为空或特定的无锁标识,表明对象不受任何同步控制,任何线程都能够无障碍地访问该对象。

无锁的标志位为01,即如果是否偏向锁标识为0时是无锁状态,为1时是偏向锁。在这个状态下,没有线程拥有锁,并且存储了对象的hashcode、对象的分代年龄以及是否为偏向锁的标志(0表示不是偏向锁)。

当一个线程首次尝试获取锁时,JVM会检查这个锁是否处于无锁状态。如果是,JVM会尝试将锁偏向给这个线程,也就是将锁标记为偏向这个线程,并且将这个线程的ID记录在锁的标记中。这样,当这个线程再次尝试获取锁时,就可以避免一些昂贵的操作,因为JVM可以直接检查锁是否仍然偏向这个线程。

偏向锁

当一个线程首次成功获取一个锁时,锁就进入了偏向锁状态。在偏向锁状态下,只有持有偏向锁的线程才能再次获取这个锁,而不会引起竞争。如果其他线程尝试获取这个锁,偏向锁就会升级为轻量级锁。

偏向锁的标志位为01,即是否偏向锁表标识位为1。与无锁状态的标志位相同,但存储的内容有所不同。偏向锁状态下,会存储偏向的线程ID、偏向时间戳、对象分代年龄以及是否偏向锁的标志(1)。

偏向锁是一种针对线程独占锁优化的机制,它适用于单一线程长时间、连续地访问同一段同步代码的情况。当某个线程首次获得同步代码块的锁后,Java虚拟机会在对象头的Mark Word中记录该线程的ID,形成偏向锁。在此之后,该线程再次进入同步代码块时,无需执行CAS操作等复杂的同步动作,仅需确认Mark Word中的偏向线程ID是否为自己,便可迅速获得锁,从而极大地减少了获取锁的开销,提升了并发性能。

在偏向锁生效期间,除非有其他线程尝试获取该锁,否则持有偏向锁的线程不会主动释放锁。当出现锁竞争时,原有的偏向锁持有者会经历撤销过程。此过程发生在全局安全点,即在所有线程均停止执行字节码的时刻,JVM会暂停当前持有偏向锁的线程,检查锁对象的状态。如果发现持有偏向锁的线程不再活动或者锁确实处于被争夺状态,则会撤销偏向锁,即将对象头恢复为无锁状态(标志位为01)或直接升级为轻量级锁(标志位调整为对应轻量级锁的状态)。

偏向锁主要是为了解决在一个线程连续多次获取同一锁的情况,降低不必要的同步操作开销。当首次获取锁的线程再次进入同步代码块时,会检查对象头中存储的线程ID是否与当前线程一致。如果一致,则直接获得锁;如果不一致,则需要撤销偏向锁,重新进行锁竞争,可能升级为轻量级锁。

优点
对于没有或很少发生锁竞争的场景,偏向锁可以显著减少锁的获取和释放所带来的性能损耗。

缺点

  • 额外存储空间:偏向锁会在对象头中存储一个偏向线程ID等相关信息,这部分额外的空间开销虽然较小,但在大规模并发场景下,累积起来也可能成为可观的成本。

  • 锁升级开销:当一个偏向锁的对象被其他线程访问时,需要进行撤销(revoke)操作,将偏向锁升级为轻量级锁,甚至在更高竞争情况下升级为重量级锁。这个升级过程涉及到CAS操作以及可能的线程挂起和唤醒,会带来一定的性能开销。

  • 适用场景有限:偏向锁最适合于绝大部分时间只有一个线程访问对象的场景,这样的情况下,偏向锁的开销可以降到最低,有利于提高程序性能。但如果并发程度较高,或者线程切换频繁,偏向锁就可能不如轻量级锁或重量级锁高效。

轻量级锁

当一个线程尝试获取一个已经被其他线程持有的偏向锁时,偏向锁会升级为轻量级锁。轻量级锁是一种用于处理线程之间轻量级竞争的机制。当一个线程尝试获取轻量级锁时,它会先自旋一段时间,尝试等待锁被释放。如果在这段时间内锁被释放了,那么这个线程就可以成功获取锁。如果自旋结束后锁仍然被持有,那么这个线程就会尝试将锁升级为重量级锁。

轻量级锁的标识位为:00。当锁从偏向锁升级为轻量级锁时,标志位会变为00。在轻量级锁状态下,多个线程可能会尝试获取锁,通过自旋来等待锁被释放。

轻量级锁利用CAS操作尝试将对象头的Mark Word替换为指向线程栈中锁记录的指针,如果CAS操作成功,则表示线程成功获取锁。获取锁失败的线程会进入自旋状态,不断循环尝试获取锁,直到获取成功或升级为重量级锁。在自旋期间,线程不会立即进入阻塞状态,而是不断循环检查锁是否可用。这种机制可以减少线程上下文切换的开销,但如果自旋次数过多或者竞争加剧,自旋就会失去意义,JVM会选择升级为重量级锁。

优点

  • 低开销:轻量级锁通过CAS操作尝试获取锁,避免了重量级锁中涉及的线程挂起和恢复等高昂开销。
  • 快速响应:在无锁竞争或者锁竞争不激烈的情况下,轻量级锁使得线程可以迅速获取锁并执行同步代码块。

缺点

  • 自旋消耗:当锁竞争激烈时,线程可能会长时间自旋等待锁,这会消耗CPU资源,导致性能下降。
  • 升级开销:如果自旋等待超过一定阈值或者锁竞争加剧,轻量级锁会升级为重量级锁,这个升级过程本身也有一定的开销。

重量级锁

当轻量级锁的自旋尝试达到一定阈值,或者检测到多个线程竞争激烈时,JVM会将轻量级锁升级为重量级锁。升级过程中,会取消当前线程的自旋操作,并在对象头中设置重量级锁标志。

重量级锁的标识位为:10。当锁从轻量级锁升级为重量级锁时,标志位会变为10。在重量级锁状态下,线程在获取锁时会阻塞,直到持有锁的线程释放锁。

在重量级锁状态下,线程在获取锁失败时会被操作系统挂起,放入到该对象关联的监视器(Monitor)的等待队列中,由操作系统进行线程调度,当锁被释放时,操作系统会选择合适的线程将其唤醒并授予锁。

尽管重量级锁的开销较大,涉及到线程上下文切换和内核态用户态的切换等,但它在高竞争场景下能提供稳定的互斥性和公平性,确保数据的一致性和线程的安全执行。因此,即使性能损耗较高,也是在特定情况下必要的权衡措施。

优点

  • 强一致性:重量级锁提供了最强的线程安全性,确保在多线程环境下数据的完整性和一致性。
  • 简单易用synchronized关键字的使用简洁明了,不易出错。

缺点

  • 性能开销大:获取和释放重量级锁时需要操作系统介入,可能涉及线程的挂起和唤醒,造成上下文切换,这对于频繁锁竞争的场景来说性能代价较高。
  • 延迟较高:线程获取不到锁时会被阻塞,导致等待时间增加,进而影响系统响应速度。

以上四种锁状态优缺点对比总结如下:

类型优点缺点使用场景
偏向锁快速:无须线程上下文切换,适合单一线程多次重复获取同一线程锁的场景
低开销:只需要检查对象头标记
不适合多线程竞争的场景
竞争时需要撤销偏向锁,有一定开销
大多数时候只有一线程访问同步代码块,很少出现锁竞争的情况
轻量级锁较快:通过CAS操作和自旋避免了线程的阻塞与唤醒,减少了线程上下文切换
适用于锁竞争不激烈的场景
自旋可能导致CPU空耗,在高竞争下,大量的线程自旋会增加系统负担。
无法保证绝对的公平性
短时间的同步代码块,且锁竞争不激烈,期望快速重入和释放
重量级锁稳定可靠:严格保证互斥性和公平性
能够有效应对高度竞争的锁场景
开销大:涉及到线程上下文切换,性能较低
阻塞线程可能导致响应时间变长
高并发、高竞争的场景,需要保证数据一致性,且线程等待锁的时间较长或不可预知

关于Java中锁的分类,以及各种所得介绍,请参考:阿里二面:Java中_锁的分类_有哪些?你能说全吗?

关于Java中如何定位以及避免死锁,请参考:阿里二面:如何定位&避免_死锁_?连着两个面试问到了!

锁升级的具体步骤与流程

1.无锁到偏向锁的升级流程:

  • 当线程首次尝试获取对象锁时,JVM首先检查对象是否处于无锁状态。
  • 若处于无锁状态,JVM则立即将其标记为偏向锁,并记录下当前线程的ID。
  • 这一过程通过CAS操作实现,确保线程安全地更新对象头的Mark Word为偏向锁状态,并保存偏向线程的ID。
  • 一旦设置成功,线程便可无阻碍地进入同步代码块,后续再次获取该锁时仅需验证是否仍偏向当前线程,无需额外同步操作

而对于偏向锁的释放机制:

  • 当持有偏向锁的线程正常退出同步代码块时,JVM仅简单地更新对象头的访问计数等相关信息。
  • 由于偏向锁的设计初衷是优化同一线程对锁的反复获取,因此它并不会立即释放偏向关系,而是假设下一次仍由同一线程获取锁。

2. 偏向锁到轻量级锁的升级流程:

  • 当第二个线程尝试获取已被偏向的锁时,它会首先校验对象头是否指向当前线程的ID。
  • 若校验失败,表明锁已偏向其他线程,此时需要撤销偏向锁。
  • 撤销后,对象会回到无锁状态或过渡至轻量级锁状态。
  • 接着,新线程会尝试在其栈帧中创建锁记录,并使用CAS操作将对象头的Mark Word替换为指向该锁记录的指针。
  • 若CAS操作成功,线程即获得轻量级锁;若失败,则进入自旋状态,循环尝试获取锁。

对于轻量级锁的释放机制:

  • 持有轻量级锁的线程在退出同步代码块时,会尝试通过CAS操作将对象头恢复为原始状态,即撤销锁记录指针的替换。
  • 若CAS操作成功,则轻量级锁被顺利释放;否则,可能需要进一步的锁升级或处理。

3. 轻量级锁到重量级锁的升级流程:

  • 当轻量级锁的持有线程退出同步代码块并释放锁时,它会尝试将对象头恢复到无锁或偏向锁状态。
  • 若存在多个线程竞争锁资源,轻量级锁的释放可能导致自旋线程长时间无法获取锁。
  • JVM会综合考量自旋次数、竞争激烈程度以及系统负载等因素,决策是否将轻量级锁升级为重量级锁。
  • 一旦升级为重量级锁,原持有线程必须完成锁的释放。新来的线程将被阻塞,并被加入对象的监视器(Monitor)等待队列,由操作系统负责线程的调度管理。

对于释放重量级锁:

  • 持有重量级锁的线程在退出同步代码块时,会通过调用Monitor的释放操作来唤醒等待队列中的下一个线程。
  • 被唤醒的线程将获得锁并继续执行同步代码,确保资源的顺序访问和线程安全

image.png

锁降级与锁消除

锁降级

锁降级通常出现在使用读写锁(如Java中的ReentrantReadWriteLock)的场景中。在多线程环境下,一个线程首先获取到了写锁,那么在它持有写锁期间,任何其他线程都无法获取读锁或写锁,确保了对该资源的独占访问权以进行修改。这个在持有写锁的同时,线程会尝试获取读锁。由于该线程已经持有写锁,所以它可以成功获取读锁,而不会造成死锁或其他同步问题。然后线程释会放写锁,但仍持有读锁。此时,其他线程可以获取读锁进行读取操作,但无法获取写锁进行写入操作。

锁降级的意义在于,线程在完成写操作后,如果接下来的任务主要是读取而不是继续写入,那么通过降级能够允许其他读线程同时访问资源,提高了系统的并发性能,同时保证了数据一致性,因为所有读线程看到的都是最近一次写操作完成后的一致性视图。锁降级是针对读写锁的一种高级使用方式,用于提升多读少写的并发场景性能。

锁消除

锁消除(Lock Elimination)是一种由编译器或虚拟机在运行时进行的优化技术,其目的是去除那些不必要的锁操作。当编译器或JVM的即时编译器(JIT Compiler)在分析代码时发现某个锁保护的变量并没有发生实际的共享数据竞争,也就是说,该变量的生命周期仅限于方法内部,不会逃逸出该方法,那么这个锁就可以安全地被消除掉。

例如,如果一段同步代码块中的变量只在栈上分配并且没有其他线程可以直接访问,那么即使对该变量进行了同步也不会带来任何好处,反而增加了上下文切换和锁获取释放的开销。在这种情况下,JVM可以通过逃逸分析等手段确定该变量不存在共享状态,进而消除对它的同步操作。

锁消除则是编译器和JVM层面的一种优化技术,用于消除不必要的同步,减少锁带来的性能损耗。

总结

Synchronized锁升级机制是Java虚拟机为优化多线程环境下同步操作性能而设计的一种动态调整策略。通过偏向锁、轻量级锁和重量级锁之间的智能转换,JVM可以根据实际的并发状况在低竞争和高竞争场景下分别采取不同的锁策略,从而有效减少线程上下文切换、内存占用以及CPU空转等问题,提升系统的整体并发性能。

偏向锁适用于单一线程反复访问同一锁的情况,轻量级锁则在轻度竞争场景下通过CAS和自旋优化锁的获取和释放,而重量级锁虽然开销较大,但在高强度竞争下提供了严格的互斥性和线程调度的公平性。

本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等

本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/656098.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis常用命令——Hash篇

前面我们讲述了String的相关操作命令。本篇文章主要讲解Redis中数据结构Hash的相关操作命令。希望会对你有所帮助。 目录 一、Hash哈希 二、命令 HSET HGET HEXISTS HDEL HKEYS HVALS HGETALL HMGET HLEN HSETNX HINCRBY 和 HINCRBYFLOAT 三、小结 🙋‍♂️ 作者&a…

SpringBoot整合RabbitMQ的快速使用教程

目录 一、引入依赖 二、配置rabbitmq的连接信息等 1、生产者配置 2、消费者配置 三、设置消息转换器 四、生产者代码示例 1、配置交换机和队列信息 2、生产消息代码 五、消费者代码示例 1、消费层代码 2、业务层代码 在分布式系统中,消息队列是一种重要…

【老王最佳实践-6】Spring 如何给静态变量注入值

有些时候,我们可能需要给静态变量注入 spring bean,尝试过使用 Autowired 给静态变量做注入的同学应该都能发现注入是失败的。 Autowired 给静态变量注入bean 失败的原因 spring 底层已经限制了,不能给静态属性注入值: 如果我…

【AI算法岗面试八股面经【超全整理】——机器学习】

AI算法岗面试八股面经【超全整理】 概率论信息论机器学习深度学习CVNLP 目录 1、回归损失函数2、分类损失函数3、误差(Error)、偏差(Bias)、方差(Variance)4、PCA(Principle Component Analysi…

数据库语法树优化

目录 一、σ、π、⋈ 1.选择σ 2.投影π 3.连接⋈ 二、 构建语法树 ① 解读sql语句 ② 写出关系代数表达式 ③ 画出语法树 三、优化语法树 四、练习 语法树优化方法 一、σ、π、⋈ 1.选择σ 选择就是在关系R中选择满足给定条件的诸元组。 通过条件SdeptIS选择出系别…

5,串口编程---实现简单的用串口发送接收数据

单片机通过串口向PC机发送数据 PC机通过串口接收单片机发过来的数据 1.UART和USART的区别: USART支持同步通信方式,可以通过外部时钟信号进行同步传输,而UART仅支持异步通信方式 本开发板STM32F103ZET6有5个串口,用串口1作调试串口,因为串…

【算法实战】每日一题:设计一个算法,用最少数量的矩形覆盖一系列宽度为d、高度为w的矩形,且使用矩形不能超出边界

题目 设计一个算法,用最少数量的矩形覆盖一系列宽度为d、高度为w的矩形建筑物侧墙,且矩形不能超出边界。 核心思路 考虑这种结构 前面递增后面一个与前面的某个高度一致,这时候考虑最下面的覆盖(即都是从最下面向上覆盖&#…

进程互斥经典问题(读写者问题、理发店问题)

目录 读写者问题 问题描述 问题分析 进程互斥问题三部曲 读者写者算法实现 一、找进程——确定进程关系 二、找主营业务 三、找同步约束 a.互斥 b.资源 c.配额 理发店问题 问题描述 问题分析 进程互斥问题三部曲 理发店问题算法实现 一、找进程——确定进程…

特朗普竞选带火PoliFi,以Bitget为例

以特朗普系列Meme币为代表的政治金融(PoliFi)概念币市场正在掀起热潮,前美国总统特朗普(Donald Trump)在本月稍早公开力挺加密货币,接着又在周二宣布接受比特币、以太币、SOL、USDC、DOGE…等政治献金,让相关通证高涨。 据CoinGecko数据&…

鲜花门店小程序开发流程:详细教程,让你轻松掌握

想要开发一款专属于自己鲜花门店的小程序吗?不知道从何开始?别担心,本文将为你提供详细的开发流程,帮助你轻松掌握。 1. 注册登录乔拓云网并进入操作后台 首先,你需要注册并登录乔拓云网,然后进入操作后台…

简单随机数据算法

文章目录 一,需求概述二,实现代码三、测试代码四、测试结果五、源码传送六、效果演示 一,需求概述 系统启动时,读取一组图片数据,通过接口返回给前台,要求: 图片随机相邻图片不重复 二&#…

AcWing 2568:树链剖分 ← 线段树+DFS

【题目来源】https://www.acwing.com/problem/content/2570/【题目描述】 给定一棵树,树中包含 n 个节点(编号 1∼n),其中第 i 个节点的权值为 ai。 初始时,1 号节点为树的根节点。 现在要对该树进行 m 次操作&#xf…

力扣225. 用队列实现栈

Problem: 225. 用队列实现栈 文章目录 题目描述:思路Code 题目描述: 思路 1.对一个queue模拟栈的操作,同时用一个int类型的变量topElem记录每次每次队列队尾的元素(也即是模拟stack中的stack的栈顶元素); 2…

Linux之单机项目部署

1、虚拟机(VMware)创建Linux系统 1.1、创建虚拟机 1.2、配置虚拟机IOS映射文件 1.3、虚拟机内部相关配置 等待加载即可,加载完后会弹出图形化界面,如图: 注意:一般我们做为管理员使用ROOT账号来操作&#x…

Tomcat端口配置

Tomcat是开源免费的服务器,其默认的端口为8080,本文讲述一下如何配置端口。 最后在浏览器中输入localhost:8888即可打开Tomcat界面

怎样打造一份个性化画册呢?我来教你

在这个数字化的时代,传统的照片已经不能满足我们对个性化回忆的需求。个性化画册,不仅能够承载我们的记忆,还能展现自我风格。今天,就让我来教你如何打造一份属于自己的个性化画册。 1.要制作电子杂志,首先需要选择一款适合自己的…

IT人的拖延——一放松就停不下来,耽误事?

拖延的表现 在我们的日常工作中,经常会面对这样一种情况:因为要做的Sprint ticket比较复杂或者长时间的集中注意力后,本来打算休息放松一下,刷刷剧,玩玩下游戏,但却一个不小心,没控制住时间&am…

【全开源】JAVA同城搬家系统源码小程序APP源码

JAVA同城搬家系统源码 特色功能: 强大的数据处理能力:JAVA提供了丰富的数据结构和算法,以及强大的并发处理能力,使得系统能够快速地处理大量的货物信息、司机信息、订单信息等,满足大规模物流的需求。智能路径规划&a…

常见5大开发进度盲点问题及解决方案

在软件开发项目中,识别并解决常见的进度管理盲点问题,对于确保项目按时、按预算、高质量完成至关重要。它直接关系到项目能否顺利进行,忽视任何一个问题,都可能导致项目延期、成本超支、质量下降,甚至项目失败。 因此&…

@ConfigurationProperties结合Nacos配置动态刷新之底层原理分析

Hello,我是大都督周瑜,本文给大家分析一下ConfigurationProperties结合Nacos配置动态刷新的底层原理,记得点赞、关注、分享哦! 公众号:IT周瑜 应用背景 假如在Nacos中有Data ID为common.yml的配置项: m…