volatile 和 synchronized 是 Java 中用于处理多线程并发问题的关键字

1. volatile 关键字

作用

  • 可见性volatile 确保变量的修改对所有线程立即可见。当一个线程修改了 volatile 变量,其他线程能立即看到最新的值。

  • 禁止指令重排序volatile 会禁止 JVM 对变量的读写操作进行重排序,保证操作顺序与代码顺序一致。

特点

  • 保证可见性:变量的修改对所有线程立即可见。

  • 禁止指令重排序:确保变量的读写操作顺序与代码顺序一致。

  • 不保证原子性:不能用于复合操作(如 i++)。

适用场景

  • 状态标志:用于标记线程的状态(如启动、停止)。

  • 一次性发布:确保对象的构造和初始化完成后才对其他线程可见。

  • 独立变量的可见性:当变量的写操作不依赖于当前值,且没有复合操作时。

示例

状态标志
public class VolatileExample {
    private volatile boolean running = true; // 状态标志

    public void stop() {
        running = false; // 修改 volatile 变量
    }

    public void run() {
        while (running) {
            // 执行任务
            System.out.println("Running...");
        }
        System.out.println("Stopped.");
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileExample example = new VolatileExample();
        Thread thread = new Thread(example::run);
        thread.start();

        Thread.sleep(1000); // 主线程休眠 1 秒
        example.stop(); // 修改 running 标志,停止线程
    }
}
一次性发布
public class 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;
    }
}

2. synchronized 关键字

作用

  • 原子性synchronized 保证代码块或方法的原子性,同一时间只有一个线程可以执行该代码块或方法。

  • 可见性synchronized 确保线程释放锁时,所有修改对其他线程可见。

  • 互斥性synchronized 保证同一时间只有一个线程可以访问被保护的代码块或方法。

特点

  • 保证原子性:同一时间只有一个线程可以执行被保护的代码块或方法。

  • 保证可见性:线程释放锁时,所有修改对其他线程可见。

  • 提供互斥性:同一时间只有一个线程可以访问被保护的代码。

适用场景

  • 复合操作:需要保证多个操作的原子性(如 i++)。

  • 共享资源访问:多个线程需要访问和修改共享资源时。

  • 线程同步:需要控制线程的执行顺序。

使用方式

  1. 修饰实例方法:锁是当前实例对象。

    public synchronized void method() {
        // 线程安全的代码
    }
  2. 修饰静态方法:锁是当前类的 Class 对象。

    public static synchronized void staticMethod() {
        // 线程安全的代码
    }
  3. 修饰代码块:锁是指定对象。

    public void blockMethod() {
        synchronized (this) { // 锁是当前实例对象
            // 线程安全的代码
        }
    }

示例

复合操作

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++; // 原子操作
    }

    public synchronized int getCount() {
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("Final Count: " + counter.getCount()); // 输出 2000
    }
}
共享资源访问
public class SharedResource {
    private int value = 0;

    public synchronized void setValue(int value) {
        this.value = value;
    }

    public synchronized int getValue() {
        return value;
    }

    public static void main(String[] args) {
        SharedResource resource = new SharedResource();

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                resource.setValue(resource.getValue() + 1);
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final Value: " + resource.getValue()); // 输出 2000
    }
}
线程同步
public class SynchronizedExample {
    private boolean isReady = false;

    public synchronized void waitUntilReady() throws InterruptedException {
        while (!isReady) {
            wait(); // 等待其他线程通知
        }
    }

    public synchronized void setReady() {
        isReady = true;
        notifyAll(); // 通知所有等待的线程
    }

    public static void main(String[] args) {
        SynchronizedExample example = new SynchronizedExample();

        Runnable task1 = () -> {
            try {
                example.waitUntilReady();
                System.out.println("Task 1 is running.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Runnable task2 = () -> {
            try {
                Thread.sleep(1000); // 模拟耗时操作
                example.setReady(); // 设置 ready 状态
                System.out.println("Task 2 set ready.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task2);

        thread1.start();
        thread2.start();
    }
}

volatile 和 synchronized 的区别

特性 volatile synchronized
原子性 不保证复合操作的原子性 保证代码块或方法的原子性
可见性 保证变量的可见性 保证代码块或方法内的可见性
互斥性 不提供互斥性 提供互斥性
性能 性能开销较小 性能开销较大
适用场景 状态标志、一次性发布等简单场景 复合操作、共享资源修改等复杂场景

总结

  • 使用 volatile 时,适用于简单的可见性需求,但不适合复合操作。

  • 使用 synchronized 时,适用于需要原子性和互斥性的场景,但要注意性能开销。

  • 在实际开发中,可以根据需求选择合适的关键字,或结合使用(如 volatile 修饰状态标志,synchronized 保护复合操作)。

Logo

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

更多推荐