文章目录

Java volatile和synchronized的区别与使用场景

发布于 2026-05-04 08:20:54 · 浏览 17 次 · 评论 0 条

Java volatile和synchronized的区别与使用场景

Java并发编程中,处理多线程共享数据时,volatilesynchronized 是两个最核心的关键字。它们都能保证数据的可见性,但在实现机制、功能范围和性能开销上有本质区别。正确区分和使用它们,是编写高效并发程序的关键。


一、核心概念解析

volatile 是一种轻量级的同步机制。它主要用于修饰变量,确保一个线程对变量的修改能立即对其他线程可见。它仅保证可见性和有序性(禁止指令重排),但不保证原子性。

synchronized 是一种重量级的锁机制。它可以修饰方法或代码块,确保同一时刻只有一个线程能执行被修饰的代码段。它同时保证原子性、可见性和有序性。


二、区别对照表

为了直观理解两者的差异,请参考下表:

特性 volatile synchronized
原子性 ❌ 不保证(如 i++ 操作不安全) ✅ 保证(互斥锁)
可见性 ✅ 保证(直接刷新主内存) ✅ 保证(解锁时刷新主内存)
有序性 ✅ 保证(禁止指令重排) ✅ 保证(单线程执行特性)
阻塞特性 ❌ 非阻塞,线程不挂起 ✅ 阻塞,未获取锁的线程挂起
开销 💰 低(无上下文切换) 💰💰💰 高(涉及用户态与内核态切换)

三、实操指南:如何选择与使用

根据具体的业务场景,按照以下步骤选择合适的方案。

场景一:状态标志位(使用 volatile)

当需要一个线程通过修改标志位来控制另一个线程的运行状态(如停止、启动),且不涉及其他复杂的业务逻辑时,使用 volatile 是最佳选择。

  1. 定义 一个布尔类型的变量。
  2. 添加 volatile 关键字修饰该变量。
  3. 线程 A 中修改该变量。
  4. 线程 B 中读取该变量。
public class VolatileFlagExample {
    // 1. 定义并添加 volatile 关键字
    private volatile boolean shutdownRequested = false;

    // 3. 线程 A 修改标志
    public void shutdown() {
        shutdownRequested = true;
    }

    // 4. 线程 B 读取标志
    public void doWork() {
        while (!shutdownRequested) {
            // 执行业务逻辑
        }
    }
}

场景二:复合运算或计数器(使用 synchronized)

如果操作涉及“读-改-写”三个步骤(例如 count++),或者多个变量之间存在逻辑约束,volatile 无法保证线程安全。必须使用 synchronized

  1. 创建 一个共享变量(如计数器)。
  2. 编写 一个修改该变量的方法。
  3. synchronized 关键字添加 到方法声明上。
public class SynchronizedCounter {
    private int count = 0;

    // 2. 编写方法,3. 添加 synchronized
    public synchronized void increment() {
        // count++ 包含取值、加1、写回三步,非原子操作
        count++;
    }

    public int getCount() {
        return count;
    }
}

场景三:单例模式的双重检查锁定(组合使用)

在实现单例模式时,为了减少锁的开销,通常结合两者使用。volatile 防止实例初始化过程中的指令重排,synchronized 确保只创建一个实例。

  1. 声明 单例实例变量为 volatile
  2. 编写 获取实例的静态方法。
  3. 方法内部使用 synchronized 锁定类对象。
  4. 进行 两次 null 检查。
public class DoubleCheckedLocking {
    // 1. 声明为 volatile
    private static volatile DoubleCheckedLocking instance;

    private DoubleCheckedLocking() {}

    // 2. 编写方法
    public static DoubleCheckedLocking getInstance() {
        // 4. 第一次检查(无锁)
        if (instance == null) {
            // 3. 加锁
            synchronized (DoubleCheckedLocking.class) {
                // 4. 第二次检查(有锁)
                if (instance == null) {
                    instance = new DoubleCheckedLocking();
                }
            }
        }
        return instance;
    }
}

四、决策逻辑流程

当遇到线程安全问题时,可以参照以下流程图快速决策:

graph TD A["开始: 线程安全问题"] --> B{操作是否涉及
多步复合逻辑?} B -- 是 --> C["直接使用 synchronized"] B -- 否 --> D{是否仅用于
状态标记或读写?} D -- 是 --> E["使用 volatile"] D -- 否 --> F{是否需要高性能
且能容忍并发竞争?} F -- 是 --> G["考虑 Atomic 类"] F -- 否 --> C

五、注意事项

  1. 避免synchronized 代码块中执行耗时操作(如 I/O、网络请求),以免造成死锁或性能严重下降。
  2. 切勿 仅仅为了性能盲目使用 volatile 替代 synchronized,除非满足“单一变量读写”且“不依赖当前值”的条件。
  3. 切记 volatile 不能替代锁,它无法解决像 i++ 这种需要原子性的场景。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文