Bootstrap

【JUC —— 12 JMM 】

Java 内存模型(Java Memory Model,简称 JMM)是 Java 规范的一部分,用于定义线程之间如何通过内存共享变量进行交互。JMM 在多线程环境中确保变量的可见性、原子性和有序性。

1. 什么是 JMM?

JMM 是 Java 的一部分,用于屏蔽不同硬件和操作系统的内存访问差异,保证 Java 程序在不同平台上的一致性。JMM 规范了变量在内存中的存储和读取行为,并通过一系列规则来确保多线程程序的正确性。

2. JMM 的三大特性

2.1 可见性

可见性是指一个线程对共享变量的修改,能够及时地被其他线程看到。JMM 通过以下机制保证可见性:

  • volatile 关键字:保证被 volatile 修饰的变量在被修改后立即刷新到主内存,并且从主内存读取。
  • synchronized 块:释放锁时会将变量的修改刷新到主内存,获取锁时会从主内存加载最新的变量值。
  • final字段:被final修饰的字段在构造器中一旦被初始化,其他线程就能看到它的最新值。
2.2 原子性

原子性是指某个操作是不可分割的,即使在多线程环境中也不会被打断。JMM 保证以下操作的原子性:

  • 基本数据类型的读取和赋值(long 和 double 除外)。
  • 使用 synchronized 锁定的代码块。
2.3 有序性

有序性是指程序执行的顺序按照代码的先后顺序执行。JMM 通过以下机制保证有序性:

  • happens-before 规则:JMM 提供了 happens-before 规则来确保操作之间的顺序,例如:
    • 程序次序规则:一个线程内,按照代码顺序执行。
    • 锁定规则:unlock 操作先行发生于 lock 操作。
    • volatile 变量规则:对 volatile 变量的写操作先行发生于对该变量的读操作。
    • 线程启动规则:Thread.start() 先行发生于该线程的每一个动作。
    • 线程终止规则:线程中的所有操作先行发生于该线程的终止检测(Thread.join() 方法)。
内存屏障

JMM通过内存屏障来禁止特定类型的重排序,确保内存可见性和有序性。内存屏障有以下几种:

  • LoadLoad屏障:禁止在屏障前的所有加载操作重排序到屏障之后。
  • StoreStore屏障:禁止在屏障后的所有存储操作重排序到屏障之前。
  • LoadStore屏障:禁止在屏障前的所有加载操作重排序到屏障之后。
  • StoreLoad屏障:禁止在屏障后的所有存储操作重排序到屏障之前。

 

3. JMM 的工作原理

JMM 规范了线程和主内存之间的交互。每个线程都有自己的工作内存(本地内存),工作内存保存了该线程使用的变量的副本(从主内存中拷贝而来)。线程对变量的所有操作都在工作内存中进行,变量的值只有在刷新到主内存后,其他线程才能看到。

3.1 主内存与工作内存
  • 主内存:所有共享变量(实例变量、静态变量)的存储区域。
  • 工作内存:每个线程的本地内存区域,保存了该线程使用的变量的副本。
3.2 交互操作

线程与主内存之间的交互通过以下八种操作完成:

  • lock:作用于主内存变量,把一个变量标识为线程独占状态。
  • unlock:作用于主内存变量,释放一个被线程独占的变量。
  • read:作用于主内存变量,把一个变量的值从主内存传输到线程的工作内存中。
  • load:作用于工作内存变量,把 read 操作从主内存得到的变量值放入工作内存的变量副本中。
  • use:作用于工作内存变量,把工作内存中的变量值传递给执行引擎。
  • assign:作用于工作内存变量,把执行引擎接收到的值赋给工作内存的变量。
  • store:作用于工作内存变量,把工作内存的变量值传送到主内存中。
  • write:作用于主内存变量,把 store 操作从工作内存得到的变量值放入主内存的变量中。

4. 示例代码

以下是一个简单的示例,演示 volatile 关键字的可见性保证:

在这个示例中,volatile 关键字保证了 flag 变量的可见性,

readerThread 能够及时看到 writerThread 对 flag 的修改。

public class VolatileExample {
    private static volatile boolean flag = false;

    public static void main(String[] args) {
        Thread writerThread = new Thread(() -> {
            try {
                Thread.sleep(1000); // 模拟一些工作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;
            System.out.println("Flag set to true");
        });

        Thread readerThread = new Thread(() -> {
            while (!flag) {
                // busy-waiting
            }
            System.out.println("Flag detected as true");
        });

        writerThread.start();
        readerThread.start();
    }
}

;