要理解 Java 中的 synchronized 和 volatile 关键字,核心是搞懂它们如何解决多线程并发安全问题(比如可见性、原子性、有序性)。我们先从基础概念入手,再结合代码示例对比两者的差异和适用场景。

一、前置知识:并发的三大问题

多线程并发时会遇到 3 个核心问题,这是理解两个关键字的前提:

  • 可见性:一个线程修改了共享变量,其他线程能立刻看到修改后的值(CPU 缓存会导致变量值 “不一致”)。
  • 原子性:一个操作(或多个操作)要么全部执行且执行过程不被打断,要么都不执行。
  • 有序性:程序执行顺序按代码书写顺序执行(JVM 指令重排会打乱顺序)。

二、volatile 关键字

1. 核心作用

volatile 是轻量级的同步机制,仅能修饰成员变量 / 静态变量,解决两个问题:

  • ✅ 保证可见性:修改后立刻刷新到主内存,其他线程读取时直接从主内存加载(跳过 CPU 缓存)。
  • ✅ 禁止指令重排:避免 JVM 优化导致代码执行顺序错乱(比如单例模式的双重检查锁)。
  • ❌ 不保证原子性:多线程对 volatile 变量的复合操作(如 i++)仍会线程不安全。

2. 代码示例(volatile 的可见性 + 原子性问题)

public class VolatileDemo {
    // 用volatile修饰共享变量
    private static volatile boolean flag = false;
    // 测试原子性的变量
    private static volatile int count = 0;

    public static void main(String[] args) throws InterruptedException {
        // 测试1:可见性
        new Thread(() -> {
            while (!flag) {
                // 循环直到flag变为true
            }
            System.out.println("线程1:检测到flag已修改");
        }).start();

        // 主线程延迟1秒后修改flag
        Thread.sleep(1000);
        flag = true;
        System.out.println("主线程:已将flag设为true");

        // 测试2:原子性问题(count++ 非原子操作)
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    count++; // 复合操作:读取→加1→写入,volatile无法保证原子性
                }
            }).start();
        }

        // 等待所有线程执行完毕
        Thread.sleep(2000);
        System.out.println("最终count值:" + count); // 预期100000,实际小于100000
    }
}
结果解释:

可见性:线程 1 能立刻感知到 flag 的修改,循环终止(如果不加 volatile,线程 1 会一直循环)。
原子性:count++ 最终值远小于 100000,因为多线程同时读写导致数据覆盖。

3. volatile 典型使用场景

状态标记位:如示例中的 flag,用于控制线程启动 / 停止。
单例模式(双重检查锁):禁止指令重排,避免创建半初始化对象:

public class Singleton {
    // volatile 禁止指令重排
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new Singleton(); // 不加volatile可能导致指令重排
                }
            }
        }
        return instance;
    }
}

三、synchronized 关键字

1. 核心作用

synchronized 是重量级锁(JDK 1.6 后优化了性能),解决可见性、原子性、有序性全部三个问题:

  • ✅ 保证原子性:加锁后,同一时间只有一个线程能执行锁定的代码块。
  • ✅ 保证可见性:释放锁时会将修改的变量刷新到主内存,获取锁时从主内存加载变量。
  • ✅ 保证有序性:加锁后禁止指令重排。
2. 使用方式(3 种)

表格
使用方式 锁定对象 示例
修饰实例方法 当前实例对象(this) public synchronized void method()
修饰静态方法 当前类的 Class 对象 public static synchronized void method()
修饰代码块 自定义对象(任意对象) synchronized (lockObj) { … }

3. 代码示例(解决 volatile 的原子性问题)
public class SynchronizedDemo {
    private static int count = 0;
    // 自定义锁对象(推荐:避免用this/Class对象,降低锁竞争)
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        // 1000个线程,每个线程执行100次count++
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    // 方式1:同步代码块(推荐)
                    synchronized (lock) {
                        count++; // 加锁后,复合操作变为原子性
                    }

                    // 方式2:同步静态方法(等价于synchronized (SynchronizedDemo.class))
                    // increment();
                }
            }).start();
        }

        Thread.sleep(2000);
        System.out.println("最终count值:" + count); // 精准100000
    }

    // 同步静态方法
    public static synchronized void increment() {
        count++;
    }
}

结果解释:加锁后 count++ 变为原子操作,最终值精准等于 100000,解决了并发安全问题。

四、synchronized vs volatile 核心对比
特性 synchronized volatile
修饰范围 方法、代码块 成员变量、静态变量
解决问题 可见性、原子性、有序性 可见性、有序性(无原子性)
性能 重量级(JDK1.6 后优化) 轻量级(无锁开销)
锁特性 互斥锁(独占) 无锁(仅内存语义)
适用场景 复合操作(如 i++、多步逻辑) 简单状态标记、禁止指令重排

五、关键注意事项

synchronized 锁优化:JDK 1.6 引入了偏向锁、轻量级锁、重量级锁的升级机制,大幅降低了锁的开销,不必过度担心性能问题。
volatile 不能替代 synchronized:仅当操作是单一读 / 写(如 flag = true)时用 volatile;涉及复合操作(如 i++、多步修改)必须用 synchronized 或原子类(AtomicInteger)。
锁的粒度:尽量缩小 synchronized 代码块的范围(仅包裹核心并发逻辑),减少锁竞争。
总结
核心能力:volatile 是轻量级同步,保证可见性和有序性但无原子性;synchronized 是重量级同步,解决所有并发三大问题。
选型原则:简单状态标记用 volatile,复合操作 / 多步逻辑用 synchronized。
性能权衡:volatile 无锁开销,适合高并发读、低并发写的场景;synchronized 有锁开销,但能保证完全的并发安全。

Logo

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

更多推荐