
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(); // 通知生产者
}
}
}