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,持续输出硬核文章。

目录
相关文章
|
7月前
|
Java 编译器
synchronized原理
synchronized原理
|
7月前
|
存储 监控 安全
Synchronized 实现原理
Synchronized 实现原理
64 0
架构系列——通过ReentrantLock源码分析给对象上锁的原理
架构系列——通过ReentrantLock源码分析给对象上锁的原理
|
21小时前
|
存储 Java C#
深入理解synchronized实现原理
本文深入讲解了Java中`synchronized`关键字的实现原理。`synchronized`确保同一时刻只有一个线程能进入临界区,并保证共享变量的内存可见性。它通过monitor机制实现,作用于方法时使用ACC_SYNCHRONIZED标志,作用于代码块时使用monitorenter和monitorexit指令。每个对象都有一个与之关联的monitor,线程需获取monitor锁才能执行同步代码。Monitor内部包含_EntryList、_Owner、_WaitSet等队列,管理线程的加锁、等待和唤醒过程。
深入理解synchronized实现原理
|
2月前
|
安全 Java
Synchronized是怎么实现的?
Synchronized是怎么实现的?
|
2月前
|
Java 编译器
synchronized 原理分析!
你好,我是猿java。本文详细解析了Java中`synchronized`关键字的工作原理,从对象监视器锁的角度阐述其实现机制,并介绍了锁的获取、释放及字节码级别的实现细节。同时,还探讨了锁优化技术,如偏向锁、轻量级锁、适应性自旋等,以及锁消除和锁粗化等编译器优化。通过具体的字节码分析,展示了不同类型的`synchronized`应用实例。希望对你有所帮助,欢迎关注猿java,获取更多硬核文章。
54 1
|
4月前
|
存储 Java 开发者
synchronized源码分析解读
该文章主要探讨了Java中synchronized关键字的工作原理及其相关的源码分析,概括了synchronized关键字的基本概念、特性和其实现机制。通过源码分析进一步揭示了synchronized背后的运作原理。
|
6月前
|
存储 Java C++
Synchronized底层原理
Synchronized底层原理
|
存储 安全 Java
synchronized 的底层原理
synchronized 的底层是通过 Java 中的监视器锁(monitor)来实现的。每个 Java 对象都有一个与之对应的监视器锁,当一个线程获取了该对象的监视器锁,就可以执行 synchronized 代码块或方法。其他线程只能等待该线程释放锁,才能获取该对象的监视器锁并执行 synchronized 代码块或方法。
108 0
synchronized 的底层原理
|
Java API 调度
synchronized 和 ReentrantLock 的实现原理是什么?它们有什么区别
synchronized 和 ReentrantLock 的实现原理是什么?它们有什么区别
89 0

热门文章

最新文章