synchronized 原理分析!

简介: 你好,我是猿java。本文详细解析了Java中`synchronized`关键字的工作原理,从对象监视器锁的角度阐述其实现机制,并介绍了锁的获取、释放及字节码级别的实现细节。同时,还探讨了锁优化技术,如偏向锁、轻量级锁、适应性自旋等,以及锁消除和锁粗化等编译器优化。通过具体的字节码分析,展示了不同类型的`synchronized`应用实例。希望对你有所帮助,欢迎关注猿java,获取更多硬核文章。

你好,我是猿java。

synchronized关键字是Java中用于实现线程同步的机制之一,它可以确保在同一时刻只有一个线程可以访问某个代码块或方法,从而避免线程之间的竞争条件和数据不一致的问题。这篇文章,我们将从字节码角度来剖析synchronized工作原理。

工作原理

synchronized 实现原理主要依赖于 Java对象的内置锁(也称为监视器锁,Monitor Lock)。

  1. 对象监视器(Monitor):

    • 每个Java对象都有一个与之关联的监视器(Monitor),这个监视器是实现同步的核心。
    • 当一个线程试图进入一个synchronized方法或代码块时,它必须首先获得对象的监视器锁。如果监视器锁已经被其他线程持有,那么当前线程将被阻塞,直到监视器锁被释放。
  2. 锁的获取和释放:

    • 对于实例方法,锁是对象实例的监视器。
    • 对于静态方法,锁是类对象的监视器。
    • 对于代码块,锁是指定对象的监视器。
    • 当线程进入synchronized方法或代码块时,它会尝试获取相应的监视器锁。如果锁不可用,它会进入阻塞状态,直到锁被释放。
    • 当线程退出synchronized方法或代码块时,它会释放监视器锁,从而允许其他阻塞的线程继续执行。
  3. 字节码级别的实现:

    • 在字节码级别,当编译器遇到synchronized关键字时,它会生成相应的监视器进入和退出指令。
    • 对于synchronized方法,编译器会在方法的开始和结束处插入monitorentermonitorexit指令。
    • 对于synchronized代码块,编译器会在代码块的开始和结束处插入monitorentermonitorexit指令。
  4. 偏向锁和轻量级锁:

    • 为了提高性能,Java引入了偏向锁和轻量级锁的机制。
    • 偏向锁:如果一个对象的锁被一个线程多次获取,那么该锁会偏向这个线程,从而减少获取锁的开销。
    • 轻量级锁:如果偏向锁被其他线程竞争,那么锁会升级为轻量级锁,通过自旋的方式尝试获取锁,而不是直接阻塞线程。
    • 如果竞争依然激烈,轻量级锁会升级为重量级锁,此时会导致线程阻塞。
  5. 锁的升级和降级:

    • 锁可以从偏向锁升级为轻量级锁,再升级为重量级锁。
    • 一旦锁升级为重量级锁,它不会降级为轻量级锁或偏向锁。

锁优化

在 Java中,为了提高多线程环境下的性能,Java虚拟机(JVM)对锁的实现进行了多种优化,包括偏向锁、轻量级锁、适应性自旋、锁消除和锁粗化。这些优化技术主要是为了减少锁的开销,提高并发性能。

偏向锁

偏向锁(Biased Locking)是为了减少同一线程多次获取锁的开销而设计的。

  • 原理:当一个线程首次获取锁时,锁会偏向这个线程,即在对象头中记录该线程ID。之后,如果该线程再次获取锁,不需要进行任何同步操作,只需检查对象头中的线程ID是否与当前线程匹配。
  • 撤销:如果有其他线程尝试获取偏向锁,则偏向锁会被撤销并升级为轻量级锁。

轻量级锁

轻量级锁(Lightweight Locking)是为了减少竞争不激烈的情况下的锁开销而设计的。

  • 原理:当线程尝试获取轻量级锁时,如果锁是空闲的,则通过CAS操作将锁对象的Mark Word复制到当前线程的栈帧中,并将Mark Word指向栈帧中的锁记录。如果获取成功,锁状态变为轻量级锁。
  • 自旋:如果锁已经被其他线程持有,当前线程会进行自旋,而不是直接进入阻塞状态。自旋的次数是有限的,如果超过一定次数,自旋失败,锁会升级为重量级锁。

适应性自旋

适应性自旋(Adaptive Spinning)是在轻量级锁的基础上进一步优化自旋等待的机制。

  • 原理:自旋的次数不再是固定的,而是根据前一次自旋的结果动态调整。如果前一次自旋成功,那么下一次自旋的次数会增加;如果前一次自旋失败,下一次自旋的次数会减少。
  • 优势:通过动态调整自旋次数,可以更好地适应不同的锁竞争情况,减少不必要的线程阻塞和上下文切换。

锁消除

锁消除(Lock Elimination)是编译器优化的一种技术,用于在编译期间消除不必要的锁操作。

  • 原理:在JIT编译过程中,编译器通过逃逸分析(Escape Analysis)确定某个对象是否只在单线程中使用。如果对象没有逃逸到其他线程,那么对该对象的锁操作是多余的,可以被消除。
  • 示例:在方法内部创建的局部变量对象,如果没有逃逸到方法外部或其他线程,则对该对象的同步操作可以被消除。

锁粗化

锁粗化(Lock Coarsening)是为了减少频繁获取和释放锁的开销而设计的。

  • 原理:如果编译器检测到在一段代码中频繁地对同一个对象进行加锁和解锁操作,它会将这些操作合并成一个更大的范围。在更大范围内进行一次加锁和解锁,从而减少锁的开销。
  • 示例:在循环中频繁进行加锁和解锁操作,可以将锁的范围扩大到整个循环外部。

通过上述锁优化的过程可以看出,锁优化技术的共同目标是提高多线程环境下的性能,减少锁的开销。具体来说:

  • 偏向锁:减少同一线程多次获取锁的开销。
  • 轻量级锁:减少竞争不激烈情况下的锁开销。
  • 适应性自旋:动态调整自旋次数,更好地适应不同的锁竞争情况。
  • 锁消除:在编译期间消除不必要的锁操作。
  • 锁粗化:减少频繁获取和释放锁的开销。

字节码分析

在Java中,synchronized关键字通过在字节码中插入特定的指令来实现线程同步。这些指令主要是 monitorentermonitorexit

让我们通过具体的例子和字节码分析来理解 synchronized 在不同类型上的实现。

同步实例方法

public class MyClass {
   
    public synchronized void instanceMethod() {
   
        System.out.println("Instance method");
    }
}

编译后的字节码(可以使用 javap -c MyClass 命令查看):

public synchronized void instanceMethod();
  descriptor: ()V
  flags: ACC_PUBLIC, ACC_SYNCHRONIZED
  Code:
    stack=2, locals=1, args_size=1
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String Instance method
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

分析:

  • ACC_SYNCHRONIZED 标志在方法的访问标志中,表示这是一个同步方法。
  • JVM 会在调用这个方法时自动获取和释放对象实例的监视器锁(this对象)。

同步静态方法

public class MyClass {
   
    public static synchronized void staticMethod() {
   
        System.out.println("Static method");
    }
}

编译后的字节码:

public static synchronized void staticMethod();
  descriptor: ()V
  flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
  Code:
    stack=2, locals=0, args_size=0
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String Static method
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

分析:

  • ACC_SYNCHRONIZED 标志在方法的访问标志中,表示这是一个同步方法。
  • ACC_STATIC 标志表示这是一个静态方法。
  • JVM 会在调用这个方法时自动获取和释放类对象的监视器锁(MyClass.class)。

同步代码块

public class MyClass {
   
    private final Object lock = new Object();

    public void method() {
   
        synchronized (lock) {
   
            System.out.println("Synchronized block");
        }
    }
}

编译后的字节码:

public void method();
  descriptor: ()V
  flags: ACC_PUBLIC
  Code:
    stack=3, locals=2, args_size=1
       0: aload_0
       1: getfield      #2                  // Field lock:Ljava/lang/Object;
       4: dup
       5: astore_1
       6: monitorenter
       7: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: ldc           #4                  // String Synchronized block
      12: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      15: aload_1
      16: monitorexit
      17: goto          25
      20: astore_2
      21: aload_1
      22: monitorexit
      23: aload_2
      24: athrow
      25: return
    Exception table:
       from    to  target type
           7    17    20   any
          20    23    20   any

分析:

  • monitorenter 指令在进入同步块时执行,尝试获取lock对象的监视器锁。
  • monitorexit 指令在离开同步块时执行,释放lock对象的监视器锁。
  • 字节码中包括异常处理,以确保即使在异常情况下也能正确释放锁。

从上述字节码的分析可以看出:

  • 对于同步实例方法和静态方法,字节码中使用了ACC_SYNCHRONIZED标志,JVM会自动处理锁的获取和释放。
  • 对于同步代码块,字节码中显式地插入了monitorentermonitorexit指令,以手动处理锁的获取和释放。

总结

总结来说,synchronized关键字通过对象监视器锁的机制,确保在同一时刻只有一个线程能够执行synchronized方法或代码块,从而实现线程同步。Java通过偏向锁和轻量级锁等优化手段,提高了锁的性能,减少了线程阻塞的开销。

学习交流

如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注:猿java,持续输出硬核文章。

目录
相关文章
|
23天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
15天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
20天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2574 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
18天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
3天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
2天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
159 2
|
19天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1575 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
22天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
956 14
|
3天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
198 2
|
17天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
726 10