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

面试八股之从jvm层面深入解析Java中的synchronized关键字

一、synchronized概述

synchronized是Java中最基本的同步机制,用于控制多个线程对共享资源的访问,确保同一时刻只有一个线程可以执行特定代码段或访问特定对象。它是Java内置的互斥锁实现,能够有效解决多线程环境下的原子性、可见性和有序性问题。

基本作用

  1. 原子性:确保互斥操作,防止多个线程同时执行临界区代码
  2. 可见性:保证锁释放前对共享变量的修改对其他线程可见
  3. 有序性:防止指令重排序,确保代码执行顺序符合预期

二、synchronized的三种使用方式

1. 同步实例方法

public class Counter {private int count = 0;public synchronized void increment() {count++;}
}
  • 锁对象:当前实例对象(this)
  • 作用范围:整个方法体

2. 同步静态方法

public class StaticCounter {private static int count = 0;public static synchronized void increment() {count++;}
}
  • 锁对象:当前类的Class对象(StaticCounter.class)
  • 作用范围:整个静态方法体

3. 同步代码块

public class BlockCounter {private int count = 0;private final Object lock = new Object();public void increment() {synchronized(lock) {count++;}}
}
  • 锁对象:可以是任意对象实例
  • 作用范围:代码块内部
  • 灵活性高,可以精确控制同步范围

三、JVM层面的实现原理

1. 对象头与Mark Word

在HotSpot虚拟机中,Java对象在内存中的布局分为三部分:

  1. 对象头(Header)
  2. 实例数据(Instance Data)
  3. 对齐填充(Padding)

其中对象头包含两部分:

  • Mark Word:存储对象的hashCode、GC分代年龄、锁状态等信息
  • 类型指针:指向类元数据的指针

在32位JVM中,Mark Word结构如下:

|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                  |       State        |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|-------------------------------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|-------------------------------------------------------|--------------------|
|               ptr_to_lock_record:30          | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30  | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                              | lock:2 |    Marked for GC   |
|-------------------------------------------------------|--------------------|

2. 锁升级过程

JDK1.6之后,synchronized进行了重要优化,引入了锁升级机制,而不是直接使用重量级锁。锁的状态会随着竞争情况从低到高逐步升级:

  1. 无锁状态:新创建的对象处于无锁状态
  2. 偏向锁:适用于只有一个线程访问同步块的场景
  3. 轻量级锁:当有少量线程竞争时,通过CAS操作获取锁
  4. 重量级锁:当竞争激烈时,升级为操作系统层面的互斥量
偏向锁(Biased Locking)
  • 目的:减少无竞争情况下的同步开销
  • 原理:在Mark Word中记录偏向线程ID
  • 优点:加锁解锁不需要额外操作
  • 适用场景:单线程访问同步块
轻量级锁(Lightweight Locking)
  • 目的:减少多线程交替执行同步块时的性能消耗
  • 原理:使用CAS操作将Mark Word替换为指向线程栈中锁记录的指针
  • 优点:避免线程阻塞
  • 缺点:自旋会消耗CPU
重量级锁(Heavyweight Locking)
  • 目的:处理高竞争情况
  • 原理:通过操作系统的互斥量(mutex)实现
  • 特点:线程会阻塞,性能开销大

3. 字节码层面分析

编译后的同步代码块会在字节码中使用monitorentermonitorexit指令实现:

public void syncMethod();Code:0: aload_01: dup2: astore_13: monitorenter          // 进入同步块4: aload_15: monitorexit           // 正常退出同步块6: goto          149: astore_210: aload_111: monitorexit           // 异常退出同步块12: aload_213: athrow14: return

可以看到编译器会自动生成异常处理逻辑,确保锁在异常情况下也能被释放。

四、锁优化技术

1. 自旋锁与自适应自旋

  • 自旋锁:线程不立即阻塞,而是执行忙循环(自旋)等待锁释放
  • 自适应自旋:JVM根据之前自旋等待的成功率动态调整自旋时间

2. 锁消除(Lock Elimination)

JIT编译器通过逃逸分析,发现某些锁对象不可能被共享时,会消除这些锁操作。

public String concatString(String s1, String s2, String s3) {StringBuffer sb = new StringBuffer();sb.append(s1);sb.append(s2);sb.append(s3);return sb.toString();
}

在这个例子中,StringBuffer是局部变量,不会被其他线程访问,JVM会消除其内部同步操作。

3. 锁粗化(Lock Coarsening)

将多个连续的锁操作合并为一个更大的锁操作,减少频繁同步带来的性能损耗。

public void method() {synchronized(lock) {// 操作1}synchronized(lock) {// 操作2}// 可能被优化为synchronized(lock) {// 操作1// 操作2}
}

五、性能考量与最佳实践

1. 性能比较

  • 无竞争:偏向锁 > 轻量级锁 > 重量级锁
  • 低竞争:轻量级锁 > 偏向锁 > 重量级锁
  • 高竞争:重量级锁更合适

2. 使用建议

  1. 减小同步范围:只在必要的地方加锁
  2. 降低锁粒度:使用多个锁控制不同资源
  3. 避免锁嵌套:容易导致死锁
  4. 考虑替代方案:在适当场景使用java.util.concurrent包中的并发工具

3. 示例:双重检查锁定(Double-Checked Locking)

public class Singleton {private volatile static Singleton instance;public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

注意:必须使用volatile关键字防止指令重排序问题。

六、总结

synchronized关键字是Java并发编程的基础构建块,从JDK1.0开始就存在,经过多次优化(尤其是JDK1.6的锁升级机制)后,性能已经大幅提升。理解其JVM层面的实现原理,有助于我们编写更高效、更安全的并发程序。

在实际开发中,应根据具体场景选择合适的同步策略,对于简单同步需求,synchronized仍然是一个简单有效的选择;对于更复杂的并发场景,可以考虑java.util.concurrent包中更高级的并发工具。

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

相关文章:

  • 【AI绘画】Stable Diffusion webUI 常用功能使用技巧
  • JVM 内存大对象监控和优化实践
  • AT F-Intervals 题解
  • 【KO】大厂常见问题
  • 局域网远程控制/推流
  • 从裸机到云原生:Linux 操作系统实战进阶的“四维跃迁”
  • 嵌入式调试利器:STM32F429移植letter-shell实战
  • 【第四章:大模型(LLM)】05.LLM实战: 实现GPT2-(7)模型训练与微调
  • Apache 服务器基础配置与虚拟主机部署
  • 【自动化备份全网服务器数据项目】
  • 前端,route路由
  • 计算机视觉(7)-纯视觉方案实现端到端轨迹规划(思路梳理)
  • Rsync自动化备份平台建设实战
  • C#对接Ollama,调用大模型禁用思考模式
  • 鸿蒙本地与云端数据双向同步实战:从原理到可运行 Demo 的全流程指南
  • HarmonyOS元服务开发系列教程(三):实现音乐播放和封面旋转
  • 智能家居Agent:物联网设备的统一控制与管理
  • Python函数篇:从零到精通
  • 间隙锁(Gap Lock)
  • 【YOLOV8】小目标困难场景优化
  • 计算机网络---默认网关(Default Gateway)
  • 通用同步/异步收发器USART串口
  • JavaScript的fetch函数的用法
  • C++11新增关键字和范围for循环
  • 【限时分享:Hadoop+Spark+Vue技术栈电信客服数据分析系统完整实现方案
  • 基于Python的《红楼梦》文本分析与机器学习应用
  • Uniapp物联网平台登录与温湿度监测系统
  • 【电子硬件】EMI中无源晶振的优势
  • 从原理到实践:一文掌握Kafka的消息生产与消费
  • Web前端小游戏轮盘。