返回

多线程并发下的Java内存模型JMM

后端

导语

在并发编程的世界中,Java内存模型(JMM)是一个至关重要的概念。它定义了多线程环境中如何对共享内存进行操作,确保不同线程之间数据的一致性和可见性。本文将深入探究JMM的底层原理,揭示其如何驾驭多线程编程的复杂性。

内存模型抽象结构

JMM将内存抽象为一个共享的区域,其中存储着线程的局部变量和对象。每个线程都有自己独立的本地内存,用于存储私有数据。共享内存则用于存储共享变量和对象,可被多个线程访问。

重排序

为了提高性能,现代处理器会对指令进行重排序。这可能导致指令执行顺序与源代码中的顺序不同。然而,JMM保证了重排序不会破坏程序的语义,即重排序后的结果与未重排序时的结果相同。

先行发生原则

Happens-before 原则是JMM中的一条关键规则,用于确定事件之间何时存在因果关系。它规定了以下 8 条规则:

  1. 程序顺序规则: 程序中按顺序执行的任何操作。
  2. 监视器锁定规则: 线程获得锁之前执行的操作先行于释放锁之后执行的操作。
  3. volatile 变量规则: 写入 volatile 变量的操作先行于从 volatile 变量读取的操作。
  4. 线程启动规则: 线程启动前执行的操作先行于该线程执行的任何操作。
  5. 线程终止规则: 线程执行的任何操作先行于该线程终止后的任何操作。
  6. 线程中断规则: 中断线程之前执行的操作先行于该线程处理中断后的任何操作。
  7. 对象终结规则: 对象构造完成之后执行的操作先行于该对象的 finalize() 方法执行。
  8. 传递性: 如果 A 先行于 B,而 B 先行于 C,那么 A 也先行于 C。

实例

以下代码示例演示了 JMM 的原理:

public class JMMExample {

    private int counter = 0;
    private volatile boolean flag = false;

    public void incrementCounter() {
        counter++;
        flag = true;
    }

    public boolean isFlagTrue() {
        return flag;
    }

    public int getCounter() {
        return counter;
    }
}

在一个线程中,我们可以调用 incrementCounter() 方法,而在另一个线程中,我们可以检查 isFlagTrue() 和 getCounter() 方法的值。根据 Happens-before 规则,以下情况将永远不会发生:

  • flag 为 false,但 counter 不为 0
  • flag 为 true,但 counter 为 0

结论

Java 内存模型 (JMM) 是并发编程的基石,它确保了多线程环境中共享内存操作的正确性。通过理解 JMM 的底层原理和 Happens-before 原则,开发者可以构建健壮且可扩展的并发应用程序。