概述
Java 并发编程中的两个核心关键字:synchronized 和 volatile。它们都是为了解决多线程环境下的数据一致性问题,但在作用机制、保证的特性以及适用场景上有着本质的区别。
简单来说: synchronized 是一把'重量级的锁',它通过互斥访问来保证原子性、可见性和有序性。 volatile 是一个'轻量级的同步机制',它主要保证可见性和有序性,但不保证原子性。
本文详细解析了 Java 并发编程中的两个核心关键字:synchronized 和 volatile。synchronized 通过监视器锁实现互斥访问,保证原子性、可见性和有序性,适用于复杂临界区保护;volatile 通过内存屏障保证可见性和有序性,但不保证原子性,适用于状态标志控制及单例模式等场景。文章对比了两者的核心区别、优缺点及适用场景,并提供了代码示例。

Java 并发编程中的两个核心关键字:synchronized 和 volatile。它们都是为了解决多线程环境下的数据一致性问题,但在作用机制、保证的特性以及适用场景上有着本质的区别。
简单来说: synchronized 是一把'重量级的锁',它通过互斥访问来保证原子性、可见性和有序性。 volatile 是一个'轻量级的同步机制',它主要保证可见性和有序性,但不保证原子性。
synchronized 是 Java 中最基础、最常用的同步机制,它通过获取和释放对象的'监视器锁'(Monitor Lock)来实现线程间的互斥访问。
synchronized 可以修饰方法或代码块,锁定的对象不同,其作用范围也不同。
public class Counter {
private int count = 0;
// 锁定的是当前对象实例 (this)
public synchronized void increment() {
count++; // 这个操作是原子的
}
public synchronized int getCount() {
return count;
}
}
锁对象 当前对象实例 (this)。 作用范围 同一个对象实例的多个 synchronized 实例方法之间是互斥的。不同对象实例的 synchronized 方法可以并发执行。
public class GlobalCounter {
private static int globalCount = 0;
// 锁定的是当前类的 Class 对象 (GlobalCounter.class)
public static synchronized void incrementGlobal() {
globalCount++;
}
public static synchronized int getGlobalCount() {
return globalCount;
}
}
锁对象 该类的 Class 对象。 作用范围 无论创建多少个类的实例,所有线程在调用该类的 synchronized 静态方法时,都会竞争同一把锁,实现全局互斥。
public class FineGrainedCounter {
private int countA = 0;
private int countB = 0;
private final Object lockA = new Object();
private final Object lockB = new Object();
// 只锁定操作 countA 的部分,提高并发度
public void incrementA() {
synchronized(lockA) { // 锁定指定的对象 lockA
countA++;
}
}
// 只锁定操作 countB 的部分
public void incrementB() {
synchronized(lockB) { // 锁定指定的对象 lockB
countB++;
}
}
// 锁定当前对象实例
public void doSomething() {
synchronized(this) {
// ... 临界区代码
}
}
}
锁对象 synchronized 括号内指定的任意对象。 作用范围 灵活性最高。可以精确控制需要同步的代码范围,避免将整个方法都锁定,从而减少锁的竞争,提高并发性能。
JVM 通过对象内部的'监视器锁'(Monitor)来实现 synchronized。在字节码层面:
为了优化性能,JDK 1.6 引入了锁升级机制:
适用于需要对共享资源进行复杂操作、保证操作原子性的场景,例如:
volatile 是一个变量修饰符,它不提供任何互斥机制,而是通过内存屏障(Memory Barrier)来保证变量的可见性和禁止指令重排序。
volatile 只能用来修饰变量。
public class VolatileExample {
// 修饰一个布尔标志位,用于线程间通信
private volatile boolean shutdownRequested = false;
// 修饰一个对象引用
private volatile Config config;
// 线程 A:设置标志位
public void shutdown() {
shutdownRequested = true; // 写操作,会立即刷新到主内存
}
// 线程 B:检查标志位
public void doWork() {
while(!shutdownRequested) { // 读操作,每次都从主内存读取最新值
// ... 执行任务
}
// 收到关闭请求,优雅退出
}
// 注意:以下操作不是原子的!
private volatile int counter = 0;
public void unsafeIncrement() {
counter++; // 读 - 改 - 写,非原子操作,多线程下结果可能错误
}
}
volatile 的实现主要依赖于 CPU 的缓存一致性协议(如 MESI)和 JVM 插入的内存屏障指令。它告诉 JVM 和 CPU,这个变量是'易变的',不能对其进行激进的优化(如缓存、重排序)。
适用于'一个线程写,多个线程读',且写操作是原子的(通常是直接赋值)的场景:
public class Singleton {
// volatile 防止 instance = new Singleton() 指令重排序
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) { // 第一次检查
synchronized(Singleton.class) {
if(instance == null) { // 第二次检查
instance = new Singleton(); // 可能发生重排序
}
}
}
return instance;
}
}
| 特性 | synchronized | volatile |
|---|---|---|
| 作用对象 | 方法、代码块 | 变量 |
| 核心机制 | 互斥锁 (Monitor) | 内存屏障 (Memory Barrier) |
| 原子性 | 保证 (通过互斥实现) | 不保证 (仅保证单次读/写原子) |
| 可见性 | 保证 (进出同步块时刷新主内存) | 保证 (强制读写主内存) |
| 有序性 | 保证 (通过互斥和禁止重排序) | 保证 (通过内存屏障禁止重排序) |
| 线程阻塞 | 会阻塞 (未获取锁的线程进入阻塞状态) | 不会阻塞 (线程可以继续执行) |
| 性能开销 | 较大 (涉及操作系统,可能上下文切换) | 较小 (主要是内存屏障开销) |
| 适用场景 | 复杂的原子操作、临界区保护 | 简单的状态标志、一次性安全发布、DCL 单例模式 |
仅需保证可见性进需要操作是原子的 (如 flag = true): 优先使用 volatile,因为它更轻量。
class TaskRunner {
private volatile boolean stopped = false; // 线程安全的状态标志
public void run() {
while(!stopped) { /* 执行任务 */ }
}
public void stop() {
stopped = true; // 修改立即可见
}
}
volatile 防止 new Singleton() 的分解步骤重排序,避免返回未初始化的对象
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;
}
}
强制原子性,适合需要互斥访问的复合操作(如读写共享变量)。
class BankAccount {
private double balance;
public synchronized void deposit(double amount) { // 整个方法同步
balance += amount;
}
public void withdraw(double amount) {
synchronized(this) { // 代码块同步
balance -= amount;
}
}
}
synchronized 提供锁的获取/释放机制,配合 wait()/notifyAll() 实现线程间协作。
class ProducerConsumer {
private final Object lock = new Object();
private boolean isProduced = false;
public void produce() {
synchronized(lock) {
while(isProduced) {
lock.wait(); // 等待消费
}
// 生产数据...
isProduced = true;
lock.notifyAll(); // 通知消费者
}
}
public void consume() {
synchronized(lock) {
while(!isProduced) {
lock.wait(); // 等待生产
}
// 消费数据...
isProduced = false;
lock.notifyAll(); // 通知生产者
}
}
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online