JMM-内存模型

2023/7/6

Java 内存模型(JMM)是一个抽象的概念,并不真实存在,它描述了一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

JMM 关于同步的规定:

  • 线程解锁前,必须把共享变量的值刷新回主内存。
  • 线程加锁时,将清空工作内存中的共享变量,从而使用共享变量时需要从主内存重新读取最新的值(注意,加锁与解锁是同一把锁)。
  • 线程解锁前,对于该锁保护的每个更新过的共享变量,必须立即同步到主内存中。
  • 线程加锁时,对于该锁保护的每个共享变量,必须立即从主内存中读取最新的值,或者清空工作内存中的值,在使用前从主内存中重新获取。

# JMM 的主要特性:

  1. 原子性: 原子性是指一个操作是不可中断的,即使是在多个线程同时执行的时候,一个操作一旦开始,就不会被其他线程干扰。
  2. 可见性: 可见性是指当一个线程修改了某个共享变量的值,其他线程能够立即得知这个修改。JMM 通过 volatile 关键字来保证变量修改的可见性。当一个共享变量被 volatile 修饰时,它会保证修改的值立即同步到主内存,当有其他线程需要读取时,它会直接读取主内存中的最新值。
  3. 有序性: 有序性是指程序执行的顺序按照代码的先后顺序执行。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

# happens-before 原则:

这是 JMM 的重要概念,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在 happens-before 关系。几个重要的 happens-before 原则如下:

  1. 程序次序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。
  2. 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁。
  3. volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。
  4. 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。

# 示例:

考虑以下代码示例:

javaCopy codepublic class SharedObject {
    private int count = 0;

    public void incrementCount() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class ThreadExample extends Thread {
    private SharedObject sharedObject;

    public ThreadExample(SharedObject sharedObject) {
        this.sharedObject = sharedObject;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            sharedObject.incrementCount();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SharedObject sharedObject = new SharedObject();
        ThreadExample thread1 = new ThreadExample(sharedObject);
        ThreadExample thread2 = new ThreadExample(sharedObject);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("Final count is: " + sharedObject.getCount());
    }
}

在这个示例中,如果你运行这个程序,最终输出的 count 值可能会小于 2000。这是因为 count++ 操作不是原子性的,它包括读取变量 count 的当前值、增加 1 和写回新值三个步骤。由于没有同步措施,两个线程可能在同一时间执行 count++ 操作,导致一些增量丢失。

为了修复这个问题,你可以使用 synchronized 关键字或 AtomicInteger 类来确保原子性、可见性和有序性。这些工具和关键字是 Java 平台提供的并发工具,是 JMM 规则在语言层面的实现。