Java volatile和synchronized的区别与使用场景
Java并发编程中,处理多线程共享数据时,volatile 和 synchronized 是两个最核心的关键字。它们都能保证数据的可见性,但在实现机制、功能范围和性能开销上有本质区别。正确区分和使用它们,是编写高效并发程序的关键。
一、核心概念解析
volatile 是一种轻量级的同步机制。它主要用于修饰变量,确保一个线程对变量的修改能立即对其他线程可见。它仅保证可见性和有序性(禁止指令重排),但不保证原子性。
synchronized 是一种重量级的锁机制。它可以修饰方法或代码块,确保同一时刻只有一个线程能执行被修饰的代码段。它同时保证原子性、可见性和有序性。
二、区别对照表
为了直观理解两者的差异,请参考下表:
| 特性 | volatile | synchronized |
|---|---|---|
| 原子性 | ❌ 不保证(如 i++ 操作不安全) |
✅ 保证(互斥锁) |
| 可见性 | ✅ 保证(直接刷新主内存) | ✅ 保证(解锁时刷新主内存) |
| 有序性 | ✅ 保证(禁止指令重排) | ✅ 保证(单线程执行特性) |
| 阻塞特性 | ❌ 非阻塞,线程不挂起 | ✅ 阻塞,未获取锁的线程挂起 |
| 开销 | 💰 低(无上下文切换) | 💰💰💰 高(涉及用户态与内核态切换) |
三、实操指南:如何选择与使用
根据具体的业务场景,按照以下步骤选择合适的方案。
场景一:状态标志位(使用 volatile)
当需要一个线程通过修改标志位来控制另一个线程的运行状态(如停止、启动),且不涉及其他复杂的业务逻辑时,使用 volatile 是最佳选择。
- 定义 一个布尔类型的变量。
- 添加
volatile关键字修饰该变量。 - 在 线程 A 中修改该变量。
- 在 线程 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。
- 创建 一个共享变量(如计数器)。
- 编写 一个修改该变量的方法。
- 将
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 确保只创建一个实例。
- 声明 单例实例变量为
volatile。 - 编写 获取实例的静态方法。
- 在 方法内部使用
synchronized锁定类对象。 - 进行 两次
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
多步复合逻辑?} B -- 是 --> C["直接使用 synchronized"] B -- 否 --> D{是否仅用于
状态标记或读写?} D -- 是 --> E["使用 volatile"] D -- 否 --> F{是否需要高性能
且能容忍并发竞争?} F -- 是 --> G["考虑 Atomic 类"] F -- 否 --> C
五、注意事项
- 避免 在
synchronized代码块中执行耗时操作(如 I/O、网络请求),以免造成死锁或性能严重下降。 - 切勿 仅仅为了性能盲目使用
volatile替代synchronized,除非满足“单一变量读写”且“不依赖当前值”的条件。 - 切记
volatile不能替代锁,它无法解决像i++这种需要原子性的场景。

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