Java内存模型(JMM)

本文详细解释了Java内存模型(JMM)如何定义多线程环境下的内存操作规则,包括主内存、工作内存、内存屏障、原子性、可见性和有序性,以及Happens-before原则在确保线程间操作顺序中的作用。这些规则有助于避免并发编程中的常见问题。

        JVM规范中定义了java内存模型(java Memory Model,简称JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,进而达到java程序在各平台下都能达到一致的内存访问效果。        

        Java内存模型(Java Memory Model,JMM)本身是一种抽象的概念并不真实存在它描述的是一组约定或者规范,定义了Java程序在多线程环境下,对共享变量的读写操作的具体行为和规则。它确保了线程之间的可见性、有序性以及原子性。

Java内存模型包含以下几个重要概念:

  1. 主内存(Main Memory):

    • 主内存是Java内存模型中的一个概念,所有线程共享主内存。
    • 所有的变量都存储在主内存中,线程之间通过读写主内存中的变量来进行通信。
  2. 工作内存(Working Memory):

    • 每个线程都有自己的工作内存,也称为本地内存。
    • 工作内存是线程的私有内存空间,用于存储主内存中的变量的副本。
  3. 内存屏障(Memory Barriers):

    • 内存屏障是一种同步机制,用于保证特定操作的顺序性和可见性。
    • 内存屏障可以防止指令重排序和缓存不一致等问题,确保线程按照预期的顺序执行。
  4. 原子性(Atomicity):

    • 原子性指的是操作的不可分割性。
    • 在Java中,基本数据类型的读写操作具有原子性,但复合操作(例如 i++)不一定具有原子性,可能会出现线程安全问题。
  5. 可见性(Visibility):

    • 可见性指的是一个线程对共享变量的修改能够被其他线程及时地感知到。
    • Java内存模型通过使用内存屏障和缓存一致性协议来确保可见性。
  6. 有序性(Ordering):

    • 有序性指的是程序执行的结果按照特定的顺序来进行排序,与代码编写的顺序不一定完全一致。
    • Java内存模型通过使用内存屏障来禁止不必要的指令重排序,确保有序性。

Java内存模型的规则和保证确保了多线程程序的正确性和可靠性。开发者可以利用这些规则来编写线程安全的代码,并避免常见的并发问题,如数据竞争、死锁等。同时,也可以通过显式地使用同步机制(如synchronized关键字、volatile关键字、Lock、Atomic类等)来实现线程间的协调和通信。

JMM规范下,三大特征

可见性(Visibility)

        可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道变更。JMM规定了所有的变量都存储在主内存中。

         系统主内存共享变量数据修改被写入是不确定的,多线程并发环境下可能出现“脏读”,所以每个线程都有自己的工作内存,线程自己的工作内存中保存了该线程使用到的变量的主内存副本的拷贝,线程变量的所有操作(读取、赋值等)都必须在线程自己的工作内存中进行,而不能够直接读写主内存中的变量。不能线程之间也不能直接访问对方工作内存中的变量,线程间的变量值的传递需要通过主内存来完成。

原子性(Atomicity)

        原子性是指同一个操作是不可打断的,即多线程环境下,操作不能被其他线程干扰、打断。

有序性(Ordering)

        对于一个线程的执行代码而言,我们总是习惯认为代码的执行是从上到下有序执行。但是为了提升性能,编译器和处理器通常会对指令序列进行重新排序。Java规范规定JVM线程内部维持顺序化语义,即只要程序的最终结果与它的顺序化执行的结果相等,那么指令的执行顺序可以与代码的顺序不一致,次过程叫指令重排。

         多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。所以在某种特定的环境下,我们需要禁止编译器的指令重排。

JMM规范下,多线程对变量的读写过程

        由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存,工作内存是每个线程的室友数据区域,而Java内存模型中规定所有的变量都存储在主内存,主内存是共享内存区域,所有的线程都可以访问,但是线程对变量的操作(读取、赋值等)必须在自己的工作内存中进行,首先要将变量从主内存拷贝到线程自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存。线程不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存的变量副本,因此不同线程间无法直接访问彼此的工作内存中的数据,线程间的通讯必须通过主内存来完成。过程如下图:

 

JMM规范下,多线程先行先发生原则之happens-before

        "Happens-Before" 是 Java 内存模型(JMM)中的一个重要概念,用于定义多线程程序中操作执行顺序的规则。它提供了一种保证,确保在正确使用同步机制时,线程之间的操作按照预期的顺序进行。

具体来说,如果事件 A "happens-before" 事件 B,则可以得出以下结论:

  1. 如果 A 发生在 B 之前,那么 A 的结果对 B 可见。
  2. 如果 A 是一个线程的写操作,而 B 是另一个线程的读操作,并且 A "happens-before" B,则 B 可以看到 A 写入的值。
  3. 如果 A 和 B 都是在同一个线程中执行的操作,并且 A 在 B 之前,则 A 的结果对 B 可见。

Java 内存模型规定以下几种情况下会发生 happens-before 关系:

  1. 程序顺序原则(Program Order Rule):在单个线程内,按照代码的先后顺序执行的操作具有 happens-before 关系。
  2. 监视器锁规则(Monitor Lock Rule):释放锁 happen-before 后续获取同一个锁。
  3. volatile变量规则(Volatile Variable Rule):对 volatile 字段的写操作 happen-before 后续针对该字段的读操作。
  4. 传递性(Transitivity):如果 A happens-before B 且 B happens-before C,则 A happens-before C。
  5. 线程启动规则(Thread Start Rule):在一个线程内,main 函数的开始 happen-before 该线程启动的任意操作。
  6. 线程加入规则(Thread Join Rule):调用一个线程的 join() 方法,会使当前线程等待直到目标线程执行完成,然后 happen-before 目标线程的所有操作。
  7. 线程中断规则(Thread Interruption Rule):对线程 interrupt 操作先行发生于被中断线程检查到中断事件的任何操作。
  8. 对象终结规则(Finalizer Rule):对象的构造函数结束 happen-before 最后一个 finalize() 方法的开始。

        这些 happens-before 规则为程序员提供了一种理解和保证多线程程序正确性的基础。通过遵循这些规则,并合理使用同步机制,可以避免并发问题和数据竞争。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值