有了synchronized为什么还需要volatile?

1 个回答

  • 凌霄
    我疯狂地奔跑,只想为你承担一切的伤害,我竖起了山丘,不愿你看见我丑陋的脸庞。
    `synchronized` 和 `volatile` 解决的是 **不同类型的并发问题**: - `synchronized` **保证** 原子性 + 可见性 - `volatile` **保证** 可见性 + 防止指令重排 #### **1. synchronized 的缺点** `synchronized` 本质上是 **一种加锁机制**,它确保多个线程对同一资源的访问是 **串行化** 的,但它有一些天然缺陷: 1. **性能损耗** - `synchronized` 在 JDK 1.6 以后虽然有 **偏向锁、轻量级锁、自旋锁等优化**,但**本质仍然是锁**,会带来 **加锁、解锁的开销**,影响性能。 2. **线程阻塞** - `synchronized` 采用 **Monitor 锁**,多个线程竞争时,未获得锁的线程会进入 **Entry Set 队列等待**,造成线程阻塞,影响系统吞吐量。 #### **2. volatile 的作用** 相比 `synchronized`,`volatile` **不会引入锁**,适用于**轻量级的线程同步**,主要有两个作用: 1. **保证变量的可见性** - 变量声明为 `volatile` 后,每次读写都会直接 **刷新主内存**,保证多个线程都能读取到最新值。 2. **防止指令重排** - `volatile` 在 **编译阶段** 和 **CPU 执行阶段** 都会插入 **内存屏障**,防止编译器或 CPU **重排序** 指令,确保代码执行的顺序符合程序员的预期。 ### **3. 具体场景分析** #### **(1) 为什么 `volatile` 适用于单例模式** 在 **双重检查锁(DCL, Double-Checked Locking)** 的单例模式中,如果不使用 `volatile`,可能会发生 **指令重排**,导致返回 **未初始化完全的对象**。 ##### **错误示例(可能出现问题)** ```java public class Singleton { private static Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 可能发生指令重排 } } } return instance; } } ``` - **可能的指令重排顺序:** 1. **分配内存** 2. **赋值给 instance 变量** 3. **执行构造方法** **问题**: - 线程 A **执行到** `instance = new Singleton();` 之前可能先**执行步骤 2**,但 **此时对象还未初始化**。 - 线程 B 读取 `instance`,会拿到**未初始化的对象**,调用方法可能出现 **空指针异常(NPE)**。 ##### **正确示例(使用 `volatile` 防止指令重排)** ```java public class Singleton { private volatile static Singleton instance; // 关键:加 volatile public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 保证不发生指令重排 } } } return instance; } } ``` **✅ 解决了指令重排问题,确保对象初始化完整。** #### **(2) volatile 适用于状态标志** 在 **多线程环境下的状态控制** 场景,`volatile` 非常适合: ```java class StopFlag { private volatile boolean stop = false; // 使用 volatile 确保可见性 public void stop() { stop = true; } public void run() { while (!stop) { // do something } } } ``` **✅ `stop` 变量如果不加 `volatile`,可能在其他线程中不可见,导致程序无法正确终止。** ### **4. `synchronized` 和 `volatile` 的区别** | 特性 | synchronized | volatile | |------|-------------|----------| | **作用** | 解决线程安全问题(原子性 + 可见性) | 保证变量可见性,防止指令重排 | | **是否阻塞线程** | 是,可能引发线程阻塞 | 否,不阻塞线程 | | **性能** | 相对较低(涉及加锁、解锁) | 更高(不会引入锁) | | **适用场景** | 需要保证多个线程访问同一资源的原子性 | 适用于轻量级状态同步(如标志变量) | ### **5. 什么时候用 `synchronized`,什么时候用 `volatile`?** - **使用 `volatile` 的场景** - 变量的更新 **不依赖其当前值**(如 **布尔标志位**)。 - 仅用于 **单次写入,多次读取** 的场景(如 **状态标志、单例模式**)。 - **避免指令重排**(如 **DCL 单例模式**)。 - **使用 `synchronized` 的场景** - 需要确保多个线程访问 **共享变量** 时的 **原子性**(如 **累加器、队列操作**)。 - 需要对 **多个变量** 进行同步,避免**竞态条件**。 ### **6. 结论** - **如果需要** 线程安全(原子性 + 可见性)-> **用 `synchronized`** - **如果只需要** 可见性(防止指令重排)-> **用 `volatile`** - **两者可以配合使用**(如 DCL 单例模式) **✅ `volatile` 并不能代替 `synchronized`,它只是对可见性和指令重排提供了保证,在某些场景下性能更优。**
    编辑于 2025-07-11 19:35 •IP属地 北京
    举报
    评论
    收藏