当前位置: 首页 > news >正文

并发编程原理与实战(二十六)深入synchronized底层原理实现

锁在保证线程安全的过程中起着关键性的作用。在第二篇文章和第十五篇文章中,我们对锁以及synchronized关键字已经有了一定的了解。下面我们对synchronized的进步一学习,深入分析其底层的实现原理。

对象监视器锁

下面摘自官方文档关于对象监视器的描述:

The Java programming language provides multiple mechanisms for
communicating between threads. The most basic of these methods is
synchronization, which is implemented using monitors. Each object in Java is
associated with a monitor, which a thread can lock or unlock. Only one thread at
a time may hold a lock on a monitor. Any other threads attempting to lock that
monitor are blocked until they can obtain a lock on that monitor. A thread t may
lock a particular monitor multiple times; each unlock reverses the effect of one
lock operation.

java编程语言提供了多种线程间通信的机制,通过监视器实现的同步是最基础的一种方法,这个是实现线程间同步的核心机制。Java中每个对象都有一个关联的监视器对象,线程可以对该对象进行加锁(lock)和解锁(unlock)操作。同一时刻仅允许一个线程持有监视器对象,其他尝试锁定该监视器的线程将被阻塞,直至获取锁成功。一个线程可多次锁定同一监视器对象,每次解锁操作抵消一次锁定。

由此可见,基于监视器实现的锁具有独占性和可重入性(多次上锁)。而synchronized正是基于对象监视器实现的锁。

synchronized使用

同步语句

我们看看synchronized修饰代码块的作用。

The synchronized statement (§14.19) computes a reference to an object; it then
attempts to perform a lock action on that object's monitor and does not proceed
further until the lock action has successfully completed. After the lock action has
been performed, the body of the synchronized statement is executed. If execution
of the body is ever completed, either normally or abruptly, an unlock action is
automatically performed on that same monitor.

当执行 synchronized 修饰的语句块时,首先计算对象的引用,随后尝试‌在该对象的监视器‌上执行锁定操作,并在锁定成功后才继续执行后续代码。如果同步块内的代码执行完成,无论正常结束或异常中断,在该监视器上将会自动执行一个解锁操作。

同步方法

我们再看看synchronized修饰实例方法和静态方法的作用。

A synchronized method (§8.4.3.6) automatically performs a lock action when it is
invoked; its body is not executed until the lock action has successfully completed. If
the method is an instance method, it locks the monitor associated with the instance
for which it was invoked (that is, the object that will be known as this during
execution of the body of the method). If the method is static, it locks the monitor
associated with the Class object that represents the class in which the method is
defined. If execution of the method's body is ever completed, either normally or
abruptly, an unlock action is automatically performed on that same monitor.

一个synchronized修饰符的方法被调用时将会执行一个锁定操作,synchronized代码块中的代码不会执行直到锁定操作成功完成。如果方法是一个实例方法,锁定的是当前实例关联的监视器(即方法体内的 this 对象)。如果方法是一个静态方法,锁定的该方法所属类的 Class 对象关联的监视器。如果方法体执行完成,无论正常结束或异常中断,在该监视器上将会自动执行一个解锁操作。

基于上述语法说明,才有了我们常见的synchronized的三种用法,回顾下代码,如下:

public class SynchronizedTest {public static void main(String[] args) {System.out.println("synchronized class");}//修饰普通方法public synchronized void output(String name) {for(int i=0; i<5; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(name);}}//修饰代码块public void output2(String name) {synchronized(this) {for(int i=0; i<5; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(name);}}}//修饰静态方法public static synchronized void output3(String name) {for(int i=0; i<5; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(name);}}
}

死锁处理机制

在使用synchronized加锁时,开发者并不需要代码显示的控制死锁的问题。

The Java programming language neither prevents nor requires detection of
deadlock conditions. Programs where threads hold (directly or indirectly) locks
on multiple objects should use conventional techniques for deadlock avoidance,
creating higher-level locking primitives that do not deadlock, if necessary.
Other mechanisms, such as reads and writes of volatile variables and the use
of classes in the java.util.concurrent package, provide alternative ways of
synchronization.

Java编程语言既不阻止也不要求检测‌死锁现象。若线程直接或间接持有多个对象锁,开发者需采用以下策略:使用常规的死锁避免技术,必要时创建‌高级无死锁锁原语‌。‌替代同步方案‌的其他机制,‌比如volatile 变量‌的读写操作,java.util.concurrent 包中的并发工具类,提供了其他可选的同步方案。

底层实现原理

我们使用javap -c SynchronizedTest.class命令将上述代码对应的class文件反编译成字节码指令。

C:\Program Files\Java\jdk-21\bin>javap -verbose SynchronizedTest.class
...
{public com.demo.SynchronizedTest();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 2: 0...public synchronized void output(java.lang.String);descriptor: (Ljava/lang/String;)Vflags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=2, locals=4, args_size=20: iconst_01: istore_22: iload_23: iconst_54: if_icmpge     347: ldc2_w        #21                 // long 1000l10: invokestatic  #23                 // Method java/lang/Thread.sleep:(J)V13: goto          2116: astore_317: aload_318: invokevirtual #31                 // Method java/lang/InterruptedException.printStackTrace:()V21: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;24: aload_125: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V28: iinc          2, 131: goto          234: returnException table:from    to  target type7    13    16   Class java/lang/InterruptedExceptionLineNumberTable:line 8: 0line 10: 7line 13: 13line 11: 16line 12: 17line 14: 21line 8: 28line 16: 34StackMapTable: number_of_entries = 4frame_type = 252 /* append */offset_delta = 2locals = [ int ]frame_type = 77 /* same_locals_1_stack_item */stack = [ class java/lang/InterruptedException ]frame_type = 4 /* same */frame_type = 250 /* chop */offset_delta = 12public void output2(java.lang.String);descriptor: (Ljava/lang/String;)Vflags: (0x0001) ACC_PUBLICCode:stack=2, locals=6, args_size=20: aload_01: dup2: astore_23: monitorenter4: iconst_05: istore_36: iload_37: iconst_58: if_icmpge     4011: ldc2_w        #21                 // long 1000l14: invokestatic  #23                 // Method java/lang/Thread.sleep:(J)V17: goto          2720: astore        422: aload         424: invokevirtual #31                 // Method java/lang/InterruptedException.printStackTrace:()V27: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;30: aload_131: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V34: iinc          3, 137: goto          640: aload_241: monitorexit42: goto          5245: astore        547: aload_248: monitorexit49: aload         551: athrow52: returnException table:from    to  target type11    17    20   Class java/lang/InterruptedException4    42    45   any45    49    45   any...public static synchronized void output3(java.lang.String);descriptor: (Ljava/lang/String;)Vflags: (0x0029) ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZEDCode:stack=2, locals=3, args_size=10: iconst_01: istore_12: iload_13: iconst_54: if_icmpge     347: ldc2_w        #21                 // long 1000l10: invokestatic  #23                 // Method java/lang/Thread.sleep:(J)V13: goto          2116: astore_217: aload_218: invokevirtual #31                 // Method java/lang/InterruptedException.printStackTrace:()V21: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;24: aload_025: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V28: iinc          1, 131: goto          234: returnException table:from    to  target type7    13    16   Class java/lang/InterruptedException...
}
SourceFile: "SynchronizedTest.java"C:\Program Files\Java\jdk-21\bin>

从上面可以看出,synchronized修饰普通方法和修饰静态方法时,字节码文件方法中会有同步标志 ACC_SYNCHRONIZED,而修饰代码块时使用了生成显式的 monitorenter/monitorexit 指令。ACC_SYNCHRONIZED 是方法的访问标志位,编译时由编译器添加,无需生成显式的 monitorenter/monitorexit 指令。两种上锁和释放锁的方式是有区别的:

(1)当synchronized修饰的普通方法和修饰静态方法时方法被调用时,JVM 检查是否设置ACC_SYNCHRONIZED 标志,若设置了线程会‌隐式尝试获取锁‌:对于实例方法锁对象是当前实例(this);对于静态方法锁对象是当前类的 Class 对象(如 SynchronizedTest.class);获取锁失败时线程进入阻塞状态,直到锁被释放。‌锁释放‌时无论方法正常执行结束还是抛出异常,JVM 均‌自动释放锁‌避免死锁。

(2)当执行synchronized修饰的代码块时,底层通过monitorenter指令进行上锁,我们知道,每个java对象都有一个隐式关联一个监视器(锁),调用monitorenter 指令尝试获取该监视器的所有权;调用monitorexit 指令时释放锁,异常时也会触发释放避免死锁。

http://www.dtcms.com/a/350191.html

相关文章:

  • 京东API分类接口实战指南:获取各类商品信息
  • Microsoft 365 中的 School Data Sync 功能深度解析:教育机构数字化管理的智能核心
  • Android音频学习(十五)——打开输出流
  • 如何用DeepSeek让Excel数据处理自动化:告别重复劳动的智能助手
  • 面试手写 Promise:链式 + 静态方法全实现
  • 扣子智能体商业化卡在哪?井云系统自动化交易+私域管理,闭环成交全流程拆解
  • 3491定期复盘代码实现设计模式的忌假应用
  • 使用Docker配置Redis Stack集群的步骤
  • React 19 与 Next.js:利用最新 React 功能
  • SQL性能调优
  • HTTP、HTTPS 与 WebSocket 详解
  • UDS诊断案例-新能源汽车电池管理系统(BMS)诊断
  • Git提交流程与最佳实践
  • debug kernel 的一些trace的方法
  • 嵌入式Linux内核编译与配置
  • GraphRAG
  • 掌握C++ std::invoke_result_t:类型安全的函数返回值提取利器
  • VSCode远程连接阿里云ECS服务器
  • ABB机器人焊接混合气节气阀
  • Chrome GPU 加速优化配置(前端 3D 可视化 / 数字孪生专用)
  • LangChain4J-(2)-高阶API与低阶API
  • 从人工巡检到AI预警:智慧工地如何用技术重构施工安全体系
  • Dubbo3.3 Idea Maven编译命令
  • 指纹手机技术支持体系:从核心技术到场景化落地保障
  • (四十六)深度解析领域特定语言(DSL)第八章——语法分析器组合子:案例实现(Part2)
  • Spring Boot 集成 Eclipse Mosquitto
  • 2025生成式引擎优化(GEO)技术研究报告:技术演进、行业应用与服务商能力选择指南
  • 【小增长电商技术分享】电商支付宝批量转账工具技术测评:架构特性、合规风险与选型方法论,支付宝官方|小增长|云方付|易推客省心返
  • 深度学习——神经网络简单实践(在乳腺癌数据集上的小型二分类示例)
  • 深度学习入门:从概念到实战,用 PyTorch 轻松上手