一、volatile 关键字

1. 核心作用

volatile 是轻量级的同步机制,仅能修饰成员变量(实例变量 / 类变量),核心解决两个问题:

  • 可见性:一个线程修改了 volatile 变量的值,其他线程能立刻看到最新值(避免线程缓存导致的 “脏读”);
  • 禁止指令重排序:保证变量的读写操作按代码顺序执行(比如单例模式的双重检查锁);
  • ❗ 重要:不保证原子性(比如 count++ 这种复合操作依然会线程不安全)。
2. 基本用法

直接修饰变量,语法:volatile 数据类型 变量名;

3. 实用示例(状态标记位)

volatile 最典型的场景是 “线程开关 / 状态标记”,因为这类场景只需要可见性,不需要原子操作:

public class VolatileDemo {
    // 用 volatile 修饰状态标记,保证多线程可见性
    private volatile boolean isRunning = true;

    // 工作线程
    class WorkerThread extends Thread {
        @Override
        public void run() {
            // 线程循环执行,直到 isRunning 变为 false
            while (isRunning) {
                // 模拟业务逻辑(空循环仅作演示)
            }
            System.out.println("工作线程:检测到状态变更,停止运行");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileDemo demo = new VolatileDemo();
        WorkerThread worker = demo.new WorkerThread();
        worker.start();

        // 主线程休眠2秒,让工作线程先运行
        Thread.sleep(2000);

        // 修改状态标记,工作线程能立刻感知
        demo.isRunning = false;
        System.out.println("主线程:已设置 isRunning = false");

        // 等待工作线程结束
        worker.join();
        System.out.println("主线程:程序结束");
    }
}

运行结果

主线程:已设置 isRunning = false
工作线程:检测到状态变更,停止运行
主线程:程序结束

如果去掉 volatile,工作线程可能因为缓存了 isRunning 的值,永远无法感知到状态变更,导致无限循环。

4. 易错点:volatile 不保证原子性

下面的代码演示 volatile 无法解决复合操作的线程安全问题:

public class VolatileAtomicError {
    private volatile int count = 0;

    // 自增操作(count++ 是读-改-写复合操作)
    public void increment() {
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileAtomicError demo = new VolatileAtomicError();
        // 启动1000个线程,每个线程执行1000次自增
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.increment();
                }
            }).start();
        }

        // 等待所有线程执行完毕(简单等待,实际开发用CountDownLatch更严谨)
        Thread.sleep(2000);
        // 预期结果是1000*1000=1000000,但实际结果远小于这个值
        System.out.println("最终count值:" + demo.count);
    }
}

原因count++ 拆分为 3 步(读 count→加 1→写回),volatile 只能保证每一步的可见性,但无法保证这 3 步的原子性,多个线程会互相覆盖结果。

二、synchronized 关键字

1. 核心作用

synchronized 是重量级的同步锁,能解决原子性、可见性、有序性所有并发问题,本质是通过 “独占锁” 保证同一时刻只有一个线程执行同步代码。

2. 三种使用方式
使用方式 锁对象 适用场景
修饰实例方法 当前实例对象(this 保护实例变量的线程安全
修饰静态方法 当前类的 Class 对象 保护静态变量的线程安全
同步代码块 自定义锁对象(任意对象) 细粒度锁,只同步关键代码
3. 实用示例
示例 1:修饰实例方法(解决计数器原子性问题)《每个实例各自一把锁》
public class SynchronizedMethodDemo {
    private int count = 0;

    // 同步实例方法:锁对象是 this
    public synchronized void increment() {
        count++; // 现在是原子操作
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedMethodDemo demo = new SynchronizedMethodDemo();
        // 启动1000个线程,每个线程执行1000次自增
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.increment();
                }
            }).start();
        }

        Thread.sleep(2000);
        // 结果稳定为1000000,解决了原子性问题
        System.out.println("最终count值:" + demo.count);
    }
}
示例 2:同步代码块(细粒度锁,性能更优)

如果整个方法只有一小段代码需要同步,用同步代码块更高效(减少锁竞争):

public class SynchronizedBlockDemo {
    private int count = 0;
    // 自定义锁对象(推荐用专门的Object作为锁,避免锁对象被意外修改)
    private final Object lock = new Object();

    public void increment() {
        // 同步代码块:只锁定count++这一行,其他代码不受影响
        synchronized (lock) {
            count++;
        }
    }

    // 其余代码和示例1一致,运行结果同样稳定为1000000
    public static void main(String[] args) throws InterruptedException {
        SynchronizedBlockDemo demo = new SynchronizedBlockDemo();
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.increment();
                }
            }).start();
        }
        Thread.sleep(2000);
        System.out.println("最终count值:" + demo.count);
    }
}
示例 3:修饰静态方法(锁 Class 对象)《每个实例竞争同一把锁》
public class SynchronizedStaticDemo {
    private static int staticCount = 0;

    // 同步静态方法:锁对象是 SynchronizedStaticDemo.class
    public static synchronized void staticIncrement() {
        staticCount++;
    }

    public static void main(String[] args) throws InterruptedException {
        // 启动1000个线程修改静态变量
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    SynchronizedStaticDemo.staticIncrement();
                }
            }).start();
        }

        Thread.sleep(2000);
        System.out.println("最终staticCount值:" + SynchronizedStaticDemo.staticCount);
    }
}

三、volatile vs synchronized 核心区别

特性 volatile synchronized
修饰范围 仅能修饰成员变量 可修饰方法、同步代码块
原子性 不保证 保证
可见性 保证 保证
有序性 禁止指令重排序(保证) 保证(通过锁实现)
性能 轻量级,无阻塞 相对重量级,可能阻塞
适用场景 状态标记、单例 DCL 复合操作(如 count++)、临界区代码

总结

  1. volatile 是轻量级同步,仅保证可见性和有序性,不保证原子性,适合简单的状态标记场景;
  2. synchronized 是全能型同步锁,保证原子性、可见性、有序性,适合需要原子操作的临界区代码;
  3. 实际开发中,优先用 volatile 优化性能(能不用锁就不用),需要原子操作时再用 synchronized(或更高效的 Atomic 原子类)。

Logo

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

更多推荐