Volatile 与 Synchronized的区别是什么?

100 阅读3分钟

Volatile 与 Synchronized的区别是什么?

重要内容

特性volatilesynchronized
适用范围单个变量方法或者代码块
作用保证变量的可见性保证原子性、可见性、互斥性
原子性不保证原子性保证原子性
性能较高,轻量级较低,涉及线程锁和上下文切换
使用场景简单的状态标志位或变量需要线程安全和同步的代码块或方法

扩展知识

volatile 的使用

通过特定的内存模型规则来保证变量的可见性。它确保一个线程对 volatile变量的修改对其他线程是立即可见的

保证可见性示例

保证线程2在读取被 volatile 修饰的 flag 为实时最新的数据,如果去掉修饰 volatile ,你会发现线程2永远会在循环中

public class VolatileVisibilityExample {
    private volatile boolean flag = false; // 保证 flag 的可见性

    public void setFlag() {
        flag = true; // 修改 flag 的值
    }

    public boolean getFlag() {
        return flag; // 读取 flag 的值
    }

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

        // 创建线程1,修改 flag 的值
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(1000); // 模拟一些操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            example.setFlag(); // 设置 flag 为 true
        });

        // 创建线程2,读取 flag 的值
        Thread thread2 = new Thread(() -> {
            while (!example.getFlag()) {
                // 循环等待,直到 flag 为 true
            }
            System.out.println("Flag 已经被设置为 true");
        });

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

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

关键字 volatile 对于保证操作的原子性有非常大的帮助,但是并不能代替锁,无法保证一些复合操作的原子性

无法保证原子性示例

public class VolatileAtomicityExample {
    private volatile int count = 0;

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

    public int getCount() {
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileAtomicityExample example = new VolatileAtomicityExample();
        // 创建多个线程对 count 进行自增操作
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

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

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

        // 理论上应该是2000,但因为count++操作不是原子操作,所以结果可能小于2000
        System.out.println("count: " + example.getCount());
    }
}

volatile 保证可见性的机制

内存模型(JMM)

JMM 用于描述不同线程之间如何访问共享变量以及如何确保内存的一致性。在并发程序中,线程会在各自的工作内存(local memory)中缓存共享变量的副本。每个线程对共享变量的修改通常不会立刻反馈到主内存

volatile 关键字是 JMM 提供的一种机制,旨在确保变量的 可见性,即当一个线程修改了一个 volatile 变量时,其他线程能够立刻看到这个修改,而不是使用各自缓存中的旧值

Happens-Before 原则

Happens-Before 是 JMM 中的一种重要规则,它定义了操作之间的执行顺序。该原则描述了一个操作 发生在另一个操作之前,并且第一个操作的结果对第二个操作是可见的

Synchronized的使用

// 指定锁对象  默认锁对象就是this
synchronized(对象){} // 需要得到对象的锁,才能操作同步代码
// 直接作用于实例方法  默认锁对象就是this
public synchronized void method(){}
// 直接作用于静态方法  默认锁对象就是当前类
public static synchronized void method(){}

往期推荐

欢迎大家订阅我的合集系列内容 ~

合集系列
计算机组成原理合集
计算机网络基础合集
计算机操作系统合集
JavaSE 基础合集
系统架构设计师合集