Java高频面试之并发编程-17
volatile 和 synchronized 的区别
在 Java 并发编程中,volatile
和 synchronized
是两种常用的同步机制,但它们的适用场景和底层原理有显著差异。以下是两者的详细对比:
1. 核心功能对比
特性 | volatile | synchronized |
---|---|---|
原子性 | 不保证复合操作的原子性(如 i++ )。 | 保证代码块或方法的原子性。 |
可见性 | 强制变量读写直接操作主内存,保证可见性。 | 通过锁的释放/获取强制同步内存,保证可见性。 |
有序性 | 禁止指令重排序(内存屏障)。 | 通过锁规则保证临界区内的操作有序性。 |
线程阻塞 | 无阻塞,轻量级。 | 可能引发线程阻塞(锁竞争时)。 |
适用场景 | 单写多读、状态标志。 | 复合操作、临界区资源保护。 |
2. 原子性
-
volatile:
- 不保证原子性,仅确保单次读/写操作的原子性。
- 示例:
多线程并发时,volatile int count = 0; count++; // 非原子操作(实际是 read-modify-write)
count++
可能导致数据竞争。
-
synchronized:
- 保证原子性,锁内的所有操作作为一个整体执行。
- 示例:
synchronized (lock) {count++; // 原子操作 }
3. 可见性
-
volatile:
- 通过内存屏障强制线程每次读写
volatile
变量时直接操作主内存,跳过工作内存(CPU 缓存)。 - 示例:
volatile boolean flag = true; // 线程1修改 flag 后,线程2立即可见
- 通过内存屏障强制线程每次读写
-
synchronized:
- 线程进入同步块时清空工作内存,从主内存重新加载变量;退出同步块时将变量写回主内存。
- 示例:
synchronized (lock) {// 操作共享变量 }
4. 有序性
-
volatile:
- 通过插入内存屏障(
StoreStore
、StoreLoad
等)禁止编译器和处理器对volatile
变量的读写操作进行重排序。 - 示例:双重检查锁定(DCL)中防止对象未初始化就被引用。
private static volatile Singleton instance;
- 通过插入内存屏障(
-
synchronized:
- 通过“锁规则”实现有序性:同一时刻只有一个线程能执行同步块,天然保证操作按代码顺序执行。
- 示例:
synchronized (lock) {// 临界区内的操作不会重排序到锁外 }
5. 性能开销
-
volatile:
- 轻量级:仅通过内存屏障限制重排序,无线程阻塞和上下文切换开销。
- 适用场景:适合低竞争或单写多读场景(如状态标志)。
-
synchronized:
- 重量级:涉及锁竞争、内核态切换(重量级锁)和线程阻塞,开销较大。
- 优化机制:锁升级(偏向锁 → 轻量级锁 → 重量级锁)减少无竞争时的开销。
- 适用场景:高竞争或需要复合原子操作的场景(如计数器、资源池)。
6. 底层实现
-
volatile:
- 内存屏障:JVM 在字节码层面插入屏障指令(如
StoreStore
、LoadLoad
)。 - 硬件支持:依赖 CPU 的缓存一致性协议(如 MESI)强制刷新内存。
- 内存屏障:JVM 在字节码层面插入屏障指令(如
-
synchronized:
- Monitor 机制:基于对象头的 Mark Word 实现锁状态管理(无锁、偏向锁、轻量级锁、重量级锁)。
- 内核态操作:重量级锁依赖操作系统的互斥量(Mutex)实现线程阻塞。
7. 实际应用示例
(1) volatile 适用场景
- 状态标志:
volatile boolean shutdownRequested = false; public void shutdown() { shutdownRequested = true; } public void doWork() { while (!shutdownRequested) { /* ... */ } }
- 单例模式(DCL):
public class Singleton {private static volatile Singleton instance;public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;} }
(2) synchronized 适用场景
- 线程安全的计数器:
public class Counter {private int count = 0;public synchronized void increment() { count++; }public synchronized int getCount() { return count; } }
- 资源池管理:
public class ConnectionPool {private final LinkedList<Connection> pool = new LinkedList<>();public synchronized Connection getConnection() {while (pool.isEmpty()) wait();return pool.removeFirst();}public synchronized void release(Connection conn) {pool.addLast(conn);notifyAll();} }
8. 总结对比表
维度 | volatile | synchronized |
---|---|---|
原子性 | 仅单次读/写原子,不保证复合操作。 | 保证代码块或方法的原子性。 |
可见性 | 直接操作主内存,强制刷新。 | 通过锁同步内存。 |
有序性 | 禁止指令重排序。 | 通过锁规则保证临界区有序。 |
线程阻塞 | 无阻塞。 | 可能阻塞(锁竞争时)。 |
性能开销 | 低。 | 高(尤其是重量级锁)。 |
适用场景 | 状态标志、单例模式(DCL)。 | 复合操作、资源竞争、线程间协作。 |
选择建议
-
使用
volatile
的情况:- 变量被多个线程访问,但只有一个线程修改。
- 需要禁止指令重排序(如 DCL 单例模式)。
-
使用
synchronized
的情况:- 需要原子性操作(如
i++
、复合逻辑)。 - 多线程竞争同一资源(如连接池、计数器)。
- 需要原子性操作(如