当前位置: 首页 > 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.lryc.cn/news/618578.html

相关文章:

  • 使用pyqt5实现可勾选的测试用例界面
  • MM DEMO-2025 | 北航新融合LLM与多模态交互的无人机导航系统!AirStar,智能空中助手等你来体验
  • 前端/在vscode中创建Vue3项目
  • NoC设计中Router Table的作用
  • Day05 店铺营业状态设置 Redis
  • 【C++】迭代器失效问题
  • THCV215一种高速视频数据收发器,采用低电压差分信号(LVDS)技术支持高速串行数据传输,支持1080p/60Hz高分辨率传输
  • 软考备考(三)
  • 2-1〔O҉S҉C҉P҉ ◈ 研记〕❘ 漏洞扫描▸理论基础与NSE脚本
  • 26 届秋招建议指南
  • Git与CI/CD相关知识点总结
  • [激光原理与应用-251]:理论 - 几何光学 - 长焦与短焦的比较
  • k8s-scheduler 解析
  • 【Java项目与数据库、Maven的关系详解】
  • 正向传播与反向传播(神经网络思维的逻辑回归)
  • Gradient Descent for Logistic Regression|逻辑回归梯度下降
  • B站 韩顺平 笔记 (Day 16)
  • 微软发布GPT-5赋能的Copilot:重构办公场景的智能革命
  • MODBUS RTU协议:工业物联网的“普通话“(Android开发实战指南)
  • C++ Rust与Go
  • LeetCode算法领域经典入门题目之“Two Sum”问题
  • Springboot3多数据源案例
  • Springboot注册过滤器的三种方式(Order 排序)
  • 亚马逊后台功能风险解构:“清除并替换库存” 的致命陷阱与全链路防控策略
  • 第五章 特征值与特征向量
  • Wireshark专家模式定位网络故障:14种TCP异常深度解剖
  • 【Altium designer】快速建立原理图工程的步骤
  • 深度学习-卷积神经网络-NIN
  • Nginx反向代理功能
  • 【实时Linux实战系列】复杂实时系统中的需求管理