跳至主要內容

JUC 八股8 - 内存模型

codejavajuc八股约 1089 字大约 4 分钟

内存模型

Java 内存模型是 Java 虚拟机规范中定义的一个抽象模型,用来描述多线程环境中共享变量的内存可见性

共享变量存储在主内存中,每个线程都有一个私有的本地内存,存储了共享变量的副本。

  • 当一个线程更改了本地内存中共享变量的副本,它需要 JVM 刷新到主内存中,以确保其他线程可以看到这些更改。
  • 当一个线程需要读取共享变量时,它一半会从本地内存中读取。如果本地内存中的副本是过时的,JVM 会将主内存中的共享变量最新值刷新到本地内存中。
alt text
alt text

为什么线程要用自己的内存

线程从主内存拷贝变量到工作内存,可以减少 CPU 访问 RAM 的开销。

每个线程都有自己的变量副本,可以避免多个线程同时修改共享变量导致的数据冲突

i++ 是原子操作吗

不是

包括三个步骤:

  • 从内存中读取 i 的值。
  • 对 i 进行加 1 操作。
  • 将新的值写回内存。

三个特性

原子性要求一个操作是不可分割的,要么全部执行成功,要么完全不执行

就比如说 count++ 就不是一个原子操作,它包括读取 count 的值、加 1、写回 count 三个步骤,所以需要加锁或者使用AtomicInteger代替 int 来保证原子性

可见性要求一个线程对共享变量的修改,能够被其他线程及时看见

private static boolean flag = true;

public static void main(String[] args) {
    new Thread(() -> {
        while (flag) {} // 线程 A 可能一直看不到 flag=false
        System.out.println("线程 A 退出");
    }).start();

    try { Thread.sleep(1000); } catch (InterruptedException e) {}

    flag = false; // 线程 B 修改 flag
}

线程 A 会在本地内存中缓存 flag=true,虽然线程 B 修改了 flag=false,但不会立即同步到主内存以及线程 A 的本地内存,因此线程 A 会一直处于死循环

解决办法就是通过 volatile 关键字来保证可见性

有序性是指程序执行的顺序是否按照代码编写的顺序执行

在单线程环境下,代码能够准确无误地按照编写顺序执行。但在多线程环境下,CPU 和编译器可能会进行指令重排,代码的执行顺序因此会发生变化

简要回答:

原子性保证操作不可中断,可见性保证变量修改后线程能看到最新值,有序性保证代码执行顺序一致,可以通过 volatile、synchronized 和 CAS 机制来保证这些特性

指令重排

指令重排是指 CPU 或编译器为了提高程序的执行效率,改变代码执行顺序的一种优化技术。

从 Java 源代码到最终执行的指令序列,会经历 3 种重排序:编译器重排序、指令并行重排序、内存系统重排序。

指令重排可能会导致双重检查锁失效,比如下面的单例模式代码:

public class Singleton {
    private static Singleton instance;

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

如果线程 A 执行了 instance = new Singleton();,但构造方法还没执行完,线程 B 可能会读取到一个未初始化的对象,导致出现空指针异常

alt text
alt text

正确的方式是给 instance 变量加上 volatile 关键字,禁止指令重排

class Singleton {
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(); // 由于 volatile,禁止指令重排
                }
            }
        }
        return instance;
    }
}

happens-before

Happens-Before 是 Java 内存模型定义的一种保证线程间可见性和有序性的规则

如果操作 A Happens-Before 操作 B,那么:

  • 操作 A 的结果对操作 B 可见。
  • 操作 A 在时间上先于操作 B 执行。

换句话说,如果 A Happens-Before B,那么 A 的修改必须对 B 可见,并且 B 不能重排序到 A 之前

上次编辑于: