volatile vs synchronized:Java 并发两大"护法"

volatile和 synchronized是 Java 解决并发问题的两个核心关键字,但它们的原理、适用场景和成本完全不同。

核心区别总览

特性volatilesynchronized
本质轻量级,变量可见性保证重量级,原子性+可见性保证
作用范围只能修饰变量可以修饰方法、代码块
原子性❌ 不保证(对复合操作无效)✅ 保证
可见性✅ 保证(立即刷新到主内存)✅ 保证
有序性✅ 禁止指令重排序✅ 保证有序性
阻塞❌ 不会阻塞线程✅ 会阻塞线程(锁)
性能高(几乎无开销)低(上下文切换开销)
使用场景状态标志、双重检查锁临界区、资源争用

volatile 详解

1. 可见性保证

没有 volatile 的问题:

public class VisibilityProblem {
    private static boolean flag = false;  // ❌ 没有volatile
    
    public static void main(String[] args) throws InterruptedException {
        Thread writer = new Thread(() -> {
            try {
                Thread.sleep(1000);  // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;  // 修改标志
            System.out.println("Writer: flag 设置为 true");
        });
        
        Thread reader = new Thread(() -> {
            while (!flag) {
                // 可能永远循环!因为看不到flag的变化
            }
            System.out.println("Reader: 检测到 flag 为 true");
        });
        
        writer.start();
        reader.start();
        
        writer.join();
        reader.join();
    }
}
// 输出可能只有:Writer: flag 设置为 true
// Reader线程可能永远看不到变化

使用 volatile 解决:

public class VisibilitySolution {
    private static volatile boolean flag = false;  // ✅ 加上volatile
    
    public static void main(String[] args) throws InterruptedException {
        Thread writer = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;  // 修改会立即刷到主内存
            System.out.println("Writer: flag 设置为 true");
        });
        
        Thread reader = new Thread(() -> {
            while (!flag) {
                // 现在能看到变化了!
            }
            System.out.println("Reader: 检测到 flag 为 true");
        });
        
        writer.start();
        reader.start();
        
        writer.join();
        reader.join();
    }
}
// 正确输出:
// Writer: flag 设置为 true
// Reader: 检测到 flag 为 true

2. 禁止指令重排序

没有 volatile 的重排序问题:

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {  // 第二次检查
                    // ❌ 问题:这里可能重排序!
                    instance = new Singleton();
                    // 实际执行可能:
                    // 1. 分配内存
                    // 2. 引用赋值(instance不为null了)
                    // 3. 初始化对象
                    // 其他线程可能拿到未初始化的对象!
                }
            }
        }
        return instance;
    }
}

使用 volatile 解决:

public class Singleton {
    private static volatile Singleton instance;  // ✅ 加上volatile
    
    private Singleton() {
        // 初始化操作
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();  // ✅ 不会重排序
                }
            }
        }
        return instance;
    }
}

3. volatile 的局限性

public class VolatileLimitation {
    private volatile int count = 0;
    
    public void increment() {
        count++;  // ❌ 这不是原子操作!
        // 实际是3步:
        // 1. 读取count
        // 2. count + 1
        // 3. 写回count
        // 多线程会丢失更新
    }
    
    public static void main(String[] args) throws InterruptedException {
        VolatileLimitation demo = new VolatileLimitation();
        
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                demo.increment();
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                demo.increment();
            }
        });
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        System.out.println("最终结果: " + demo.count);
        // 可能输出:15678(而不是20000)
    }
}

synchronized 详解

1. 基本用法

public class SynchronizedDemo {
    private int count = 0;
    private final Object lock = new Object();
    
    // 1. 实例方法同步
    public synchronized void increment1() {
        count++;
    }
    
    // 2. 静态方法同步
    public static synchronized void staticMethod() {
        // 锁是 SynchronizedDemo.class
    }
    
    // 3. 同步代码块(指定锁对象)
    public void increment2() {
        synchronized (lock) {  // 使用特定对象作为锁
            count++;
        }
    }
    
    // 4. 同步代码块(this锁)
    public void increment3() {
        synchronized (this) {  // 使用当前实例作为锁
            count++;
        }
    }
}

2. 保证原子性

public class AtomicityDemo {
    private int count = 0;
    
    public synchronized void safeIncrement() {
        count++;  // ✅ 原子操作
    }
    
    public void unsafeIncrement() {
        count++;  // ❌ 非原子操作
    }
    
    public static void main(String[] args) throws InterruptedException {
        AtomicityDemo demo = new AtomicityDemo();
        
        // 测试安全版本
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                demo.safeIncrement();
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                demo.safeIncrement();
            }
        });
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        System.out.println("安全版本结果: " + demo.count);  // 总是20000
    }
}

3. 锁的升级过程

// JDK 1.6后的synchronized优化
public class LockUpgrade {
    private Object obj = new Object();
    
    public void method() {
        synchronized (obj) {  // 锁状态变化:
            // 1. 无锁 → 偏向锁(单线程)
            // 2. 偏向锁 → 轻量级锁(有竞争但不多)
            // 3. 轻量级锁 → 重量级锁(竞争激烈)
            
            // 锁消除、锁粗化等优化
        }
    }
}

实际场景对比

场景1:状态标志

// ✅ 适合用 volatile
public class ShutdownHook {
    private volatile boolean shutdownRequested = false;
    
    public void shutdown() {
        shutdownRequested = true;  // 立即对所有线程可见
    }
    
    public void doWork() {
        while (!shutdownRequested) {
            // 执行工作
        }
        System.out.println("接收到关闭信号,优雅退出");
    }
}

// ❌ 不适合用 synchronized(太重)
public class ShutdownHookHeavy {
    private boolean shutdownRequested = false;
    
    public synchronized void shutdown() {
        shutdownRequested = true;
    }
    
    public void doWork() {
        while (true) {
            synchronized (this) {
                if (shutdownRequested) {
                    break;
                }
            }
            // 执行工作
        }
        System.out.println("接收到关闭信号,优雅退出");
    }
}

场景2:计数器

// ❌ 不适合用 volatile
public class Counter {
    private volatile int count = 0;
    
    public void increment() {
        count++;  // 仍然线程不安全!
    }
    
    public int getCount() {
        return count;
    }
}

// ✅ 适合用 synchronized
public class SafeCounter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;  // 线程安全
    }
    
    public synchronized int getCount() {
        return count;
    }
}

// ✅ 更好的方案:AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;

public class BetterCounter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();  // CAS操作,无锁
    }
    
    public int getCount() {
        return count.get();
    }
}

场景3:单例模式

// 方案1:synchronized 方法(简单但性能差)
class Singleton1 {
    private static Singleton1 instance;
    
    private Singleton1() {}
    
    public static synchronized Singleton1 getInstance() {
        if (instance == null) {
            instance = new Singleton1();
        }
        return instance;
    }
}

// 方案2:双重检查锁 + volatile
class Singleton2 {
    private static volatile Singleton2 instance;
    
    private Singleton2() {}
    
    public static Singleton2 getInstance() {
        if (instance == null) {
            synchronized (Singleton2.class) {
                if (instance == null) {
                    instance = new Singleton2();
                }
            }
        }
        return instance;
    }
}

// 方案3:静态内部类(推荐)
class Singleton3 {
    private Singleton3() {}
    
    private static class Holder {
        static final Singleton3 INSTANCE = new Singleton3();
    }
    
    public static Singleton3 getInstance() {
        return Holder.INSTANCE;
    }
}

性能对比测试

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class PerformanceComparison {
    private static final int THREAD_COUNT = 100;
    private static final int LOOP_COUNT = 100000;
    
    private volatile int volatileCount = 0;
    private int synchronizedCount = 0;
    private AtomicInteger atomicCount = new AtomicInteger(0);
    
    public static void main(String[] args) throws InterruptedException {
        PerformanceComparison demo = new PerformanceComparison();
        
        System.out.println("=== volatile 测试 ===");
        long time1 = demo.testVolatile();
        System.out.println("volatile 最终值: " + demo.volatileCount);
        
        System.out.println("\n=== synchronized 测试 ===");
        long time2 = demo.testSynchronized();
        System.out.println("synchronized 最终值: " + demo.synchronizedCount);
        
        System.out.println("\n=== AtomicInteger 测试 ===");
        long time3 = demo.testAtomic();
        System.out.println("AtomicInteger 最终值: " + demo.atomicCount.get());
        
        System.out.println("\n=== 性能对比 ===");
        System.out.println("volatile 耗时: " + time1 + "ms");
        System.out.println("synchronized 耗时: " + time2 + "ms");
        System.out.println("AtomicInteger 耗时: " + time3 + "ms");
    }
    
    private long testVolatile() throws InterruptedException {
        volatileCount = 0;
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        
        long start = System.currentTimeMillis();
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int j = 0; j < LOOP_COUNT; j++) {
                    volatileCount++;  // ❌ 不安全,但测性能
                }
                latch.countDown();
            }).start();
        }
        latch.await();
        return System.currentTimeMillis() - start;
    }
    
    private long testSynchronized() throws InterruptedException {
        synchronizedCount = 0;
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        
        long start = System.currentTimeMillis();
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int j = 0; j < LOOP_COUNT; j++) {
                    incrementSync();
                }
                latch.countDown();
            }).start();
        }
        latch.await();
        return System.currentTimeMillis() - start;
    }
    
    private synchronized void incrementSync() {
        synchronizedCount++;
    }
    
    private long testAtomic() throws InterruptedException {
        atomicCount.set(0);
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        
        long start = System.currentTimeMillis();
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int j = 0; j < LOOP_COUNT; j++) {
                    atomicCount.incrementAndGet();
                }
                latch.countDown();
            }).start();
        }
        latch.await();
        return System.currentTimeMillis() - start;
    }
}

内存模型与 happens-before

volatile 的 happens-before

public class HappensBefore {
    private int x = 0;
    private volatile boolean v = false;
    
    public void writer() {
        x = 42;     // 1
        v = true;   // 2
    }
    
    public void reader() {
        if (v) {           // 3
            // 这里一定能看到 x == 42
            // 因为 1 happens-before 2
            // 2 happens-before 3(volatile写-读规则)
            // 所以 1 happens-before 3
            System.out.println(x);  // 输出42
        }
    }
}

synchronized 的 happens-before

public class SynchronizedHappensBefore {
    private int x = 0;
    
    public synchronized void writer() {
        x = 42;  // 1
    }  // 2 解锁
    
    public synchronized void reader() {
        // 3 加锁
        // 这里一定能看到 x == 42
        // 因为 1 happens-before 2(程序顺序)
        // 2 happens-before 3(监视器锁规则)
        System.out.println(x);  // 输出42
    }  // 4 解锁
}

常见错误

错误1:误用 volatile

// ❌ 错误:以为 volatile 能保证原子性
public class WrongVolatile {
    private volatile int a = 0;
    private volatile int b = 0;
    
    public void set() {
        a = 1;  // 操作1
        b = 2;  // 操作2
        // 其他线程可能看到 b=2 但 a!=1
        // volatile 不保证多个变量的原子性
    }
}

错误2:过度同步

// ❌ 错误:不需要同步的地方用了同步
public class OverSynchronized {
    private final List<String> list = Collections.synchronizedList(new ArrayList<>());
    
    public void addIfAbsent(String item) {
        synchronized (list) {  // ❌ 多余的同步
            if (!list.contains(item)) {
                list.add(item);
            }
        }
        // Collections.synchronizedList 已经同步了
    }
}

错误3:锁对象不一致

// ❌ 错误:使用不同的锁对象
public class WrongLock {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    private int count = 0;
    
    public void increment() {
        synchronized (lock1) {  // 用lock1
            count++;
        }
    }
    
    public int get() {
        synchronized (lock2) {  // ❌ 用lock2,无法保护count
            return count;
        }
    }
}

最佳实践

1. 选择原则

// 如何选择 volatile 还是 synchronized?
if (满足以下所有条件) {
    return "用 volatile";
    // 1. 对变量的写操作不依赖当前值
    // 2. 该变量没有包含在具有其他变量的不变式中
    // 3. 访问变量时不需要加锁
} else {
    return "用 synchronized 或其他锁机制";
}

2. 现代替代方案

// 1. 用 Atomic 类代替 volatile
private volatile int count;  // 旧方式
private AtomicInteger count = new AtomicInteger();  // 新方式

// 2. 用 ReentrantLock 代替 synchronized
private final ReentrantLock lock = new ReentrantLock();

public void method() {
    lock.lock();
    try {
        // 临界区
    } finally {
        lock.unlock();
    }
}

// 3. 用 StampedLock 优化读多写少
private final StampedLock lock = new StampedLock();

public void read() {
    long stamp = lock.tryOptimisticRead();
    // 读操作...
    if (!lock.validate(stamp)) {
        stamp = lock.readLock();
        try {
            // 重新读
        } finally {
            lock.unlockRead(stamp);
        }
    }
}

3. 性能优化技巧

// 1. 缩小同步范围
public void slowMethod() {
    // 很多不需要同步的代码...
    synchronized (this) {
        // 只有这一小段需要同步
    }
    // 更多不需要同步的代码...
}

// 2. 使用读写锁分离
public class ReadWriteCache {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private Map<String, Object> cache = new HashMap<>();
    
    public Object get(String key) {
        lock.readLock().lock();
        try {
            return cache.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void put(String key, Object value) {
        lock.writeLock().lock();
        try {
            cache.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

总结:

volatile 是"轻量级同步":

  • 只保证可见性和禁止重排序
  • 不保证原子性
  • 适合状态标志、双重检查锁

synchronized 是"重量级同步":

  • 保证原子性、可见性、有序性
  • 会阻塞线程
  • 适合临界区保护

选择指南:

  1. 如果只是一个线程写,多个线程读的状态标志 → volatile
  2. 如果需要复合操作的原子性 → synchronized
  3. 如果竞争不激烈 → 考虑 synchronized优化后的轻量级锁
  4. 如果读多写少 → 考虑 ReadWriteLock
  5. 如果只是计数器 → 用 Atomic类

记住核心:volatile解决可见性问题,synchronized解决原子性问题。理解它们的差异,才能写出正确高效的多线程代码。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐