当前位置:首页 > 数码 > Java-理解-中代码和内存之间的交互-内存模型-Java (JAVA流式编程)

Java-理解-中代码和内存之间的交互-内存模型-Java (JAVA流式编程)

admin8个月前 (04-16)数码27

Java 内存模型 (JMM) 是 Java 并发性的基石。它定义了线程如何通过内存进行交互以及对内存操作强制执行哪些规则。对于编写多线程应用程序的开发人员来说,了解 JMM 对于创建高效、无错误的程序至关重要。

了解 Java 内存模型

Java 内存模型 (JMM) 作为抽象层,规定 Java 程序如何与内存交互,尤其是在多线程环境中。它是 Java 语言规范的一部分,描述了线程和主内存如何通信。JMM 解决了并发执行带来的挑战,例如缓存一致性、内存一致性错误、线程争用以及编译器和处理器的指令重新排序。

通过设置一个定义和预测并发程序行为的框架,确保跨不同平台和 CPU 与内存的可预测且统一的交互。

线程和主内存交互

在 Java 中,程序创建的每个线程都有自己的堆栈,其中存储局部变量和调用信息。线程并不是孤立的;他们经常需要通信、共享对象和变量。这种通信通过主内存进行,主内存保存堆和方法区域。

JMM 描述了一个线程对共享数据(存储在堆或方法区域中)所做的更改如何以及何时对其他线程可见。这里的主要挑战是确保线程具有共享数据的最新视图,出于性能原因,这些数据可能会本地缓存在线程的堆栈中。

内存一致性错误

当不同线程对相同数据的视图不一致时,就会出现内存一致性错误。如果没有适当的内存模型,构建线程如何通过内存交互的模型几乎是不可能的,因为线程调度和可以重新排序指令的编译器优化的不可预测性。

JMM 通过提供一组称为 happens-before 的规则来帮助防止这些错误,这些规则规定了内存操作(例如读取和写入)的排序方式。

可见性和排序

可见性和排序是 JMM 提出的两个主要概念:可见性和顺序对于创建线程安全应用程序都至关重要。

如果一个线程更新的值对于应该对该更新值执行操作的另一线程不可见,则程序的行为可能会不可预测。类似地,如果操作不按顺序执行,则可能会导致线程作用于过时的数据。

happens-before

Java 内存模型的核心是 happens-before 关系。这个原则就是 Java 中保证内存一致性的规则手册。happens-before 关系提供了多线程环境中变量操作(读取和写入)的部分顺序。

以下是一些构成 JMM 内线程交互基础的关键 happens-before 规则:

  • 先写后读:一个线程写入共享变量后的操作 happens-before 任何线程从该变量读取。
  • 先解后锁:对同一锁的对象解锁操作 happens-before 任何线程对同一锁的对象加锁。
  • start() 启动线程:一个线程的 start() 方法调用 happens-before 该线程中的任何操作。
  • 线程终止:一个线程的终止 happens-before 任何其他线程检测到该线程的终止。

理解和应用这些规则可确保程序在并发环境中的行为可预测。这些规则是避免内存一致性错误、确保可见性和维护操作正确顺序的关键。

Java 内存模型应用

了解 JMM 的细节使开发人员能够编写安全且可扩展的并发应用程序。同步原语(synchronized、volatile 等)、原子变量、并发集合的正确使用都植根于 JMM。

例如,了解 volatile 变量提供的保证有助于防止过度使用同步,从而提高应用程序的性能和可扩展性。在 JMM 的上下文中,我们还考虑 as-if-serial 语义,它保证单个线程中的执行行为就像所有操作都按照它们在程序中出现的顺序执行一样—即使编译器实际上可能会在幕后重新排序指令。

Java 内存模型组件

Java 内存模型 (JMM) 是 Java 并发框架的基石,定义了 Java 线程和内存之间的交互。它指定一个线程所做的更改如何以及何时对其他线程可见,从而确保并发执行的可预测性。让我们检查一下构成 JMM 的关键组件。

共享变量

在 Java 中,被多个线程访问的变量是共享变量。这些变量存储在堆中,堆是内存的共享区域。如果处理不当,共享变量可能会成为内存一致性错误的根源。JMM 控制这些共享变量的更改如何在线程内存和主内存之间传播。

volatile 变量

Java 中的关键字 volatile 用于将 Java 变量标记为正在存储在主存中。更准确地说,这意味着对 volatile 变量的每次读取都将从主内存中读取,而不是从线程的本地缓存中读取,并且对 volatile 变量的每次写入都将写入主存。

final 变量

Java 中的关键字 final 用于将变量标记为不可变。这意味着一旦在构造函数中为 final 变量分配了值,该值就不能被更改。编译器使用这些信息进行优化,可以消除对 final 变量的某些同步检查,从而提高性能。

通过了解 Java 内存模型并正确应用其原则和组件,开发人员可以创建高效、无错误的并发应用程序。


Java 内存模型详解

Java内存模型:深入解析与实例分析

在Java并发编程的世界中,通信与同步是核心要素。Java内存模型以共享内存为主,每个线程拥有私有本地内存,共享变量则存储在主内存中。线程间的沟通通过Java内存模型(JMM)精确调控,确保本地内存的更新及时同步到主内存,并保证其他线程能够读取到最新的状态。编译器和处理器的重排序行为可能对内存可见性构成挑战,JMM通过一系列规则巧妙地管理这些复杂性,确保内存一致性。

形象地理解,当线程A更新共享变量x后,这个改变会被JMM从本地内存刷新到主内存,而线程B随后会读取到更新后的值,这就是内存模型的基本工作原理。

重排序现象分为编译器优化、指令级并行和内存系统重排序,这可能导致多线程中的微妙问题。JMM通过精细的规则,如StoreLoad屏障,来管理和禁止那些可能导致问题的特定重排序。例如,StoreLoad屏障确保内存访问顺序的全局一致性,即使在多处理器架构中也是如此。

内存模型的关键概念

JMM作为语言级别的抽象,确保跨平台的内存可见性。处理器重排序可能使内存操作的执行顺序与预期不符,现代处理器允许Store-Load重排序,但sparc-TSO和x86处理器有严格的限制。编译器通过插入内存屏障指令来维护一致性,如LoadLoad、StoreStore、LoadStore和StoreLoad屏障,其中StoreLoad是最全面的,它确保了内存操作的顺序一致性,尽管这可能会增加处理器开销。

JSR-133引入了happens-before概念,它定义了操作之间的内存可见性。这个概念并不强制操作按特定顺序执行,而是要求前一个操作对后续操作可见。happens-before关系与JMM紧密相连,简化了理解内存重排序的复杂性。数据依赖性分为三种类型,编译器和处理器会遵循这些规则,保证单线程程序的执行顺序不变。

重排序与多线程示例

在多线程编程中,重排序可能会破坏线程B读取到的共享变量值,特别是在线程A更新后。控制依赖可能导致处理器猜测执行,这在多线程中可能导致非预期的结果。JMM确保,如SynchronizedExample所示,正确同步的代码遵循顺序一致性,即所有线程看到的操作顺序保持一致。

JMM通过互斥执行来约束临界区的重排序,同时允许编译器和处理器进行性能优化。对于未正确同步的程序,JMM提供了基本的内存可见性保证,但不保证与顺序一致性模型完全一致。这些差异体现在单线程操作的顺序、long/double类型的原子性,以及处理器总线机制可能带来的非原子性读写。

在内存可见性方面,比如计算圆面积的示例,JMM确保涉及内存操作的代码对所有线程呈现一致的结果。JMM的简要概述如下:

内存可见性的核心价值在于,无论单线程还是多线程,正确同步的代码都能保证执行结果的一致性。同时,JMM为不完全同步的场景提供基础保护,确保不会出现无中生有的问题。

最后,JSR-133引入的volatile和final关键字增强了内存可见性,保证了代码的正确性和初始化安全性。总的来说,Java内存模型是一个平衡点,旨在满足程序员的编程需求,同时与编译器和处理器的优化保持兼容性。

java课程分享java多线程的内存模型

硬件的内存模型

理解

物理机并发处理的方案对于jvm的内存模型实现,也有很大的参考作用,毕竟jvm也是在硬件层上来做事情,底层架构也决定了上层的建筑建模方式。

计算机并发并非只是多个处理器都参与进来计算就可以了,会牵扯到一些列硬件的问题,最直接的就是要和内存做交互。但计算机的存储设备与处理器的预算速度相差太大,完全不能满足处理器的处理速度,怎么办,这就是后续加入的一层读写速度接近处理器运算速度的高速缓存来作为处理器和内存之间的缓冲。

高速缓存一边把使用的数据,从内存复制搬入,方便处理器快速运算,一边把运算后的数据,再同步到主内存中,如此处理器就无需等待了。

高速缓存虽然解决了处理器和内存的矛盾,但也为计算机带来了另一个问题:缓存一致性。特别是当多个处理器都涉及到同一块主内存区域的时候,将可能会导致各自的缓存数据不一致。

那么出现不一致情况的时候,以谁的为准?

为了解决这个问题,处理器和内存之间的读写的时候需要遵循一定的协议来操作,这类协议有:MSI、MESI、MOSI、Synapse、Firefly以及DragonProtocol等。这就是上图中处理器、高速缓存、以及内存之间的处理方式。

另外除了高速缓存之外,为了充分利用处理器,处理器还会把输入的指令码进行乱序执行优化,只要保证输出一致,输入的信息可以乱序执行重组,所以程序中的语句计算顺序和输入代码的顺序并非一致。

JVM内存模型

上面我们了解了硬件的内存模型,以此为借鉴,我们看看jvm的内存模型。

jvm定义的一套java内存模型为了能够跨平台达到一致的内存访问效果,从而屏蔽掉了各种硬件和操作系统的内存访问差异。这点和c和c++并不一样,C和C++会直接使用物理硬件和操作系统的内存模型来处理,所以在各个平台上会有差异,这一点java不会。

java的内存模型规定了所有的变量都存储在主内存中,java课程发现每个线程拥有自己的工作内存,工作内存保存了该线程使用到的变量的主内存拷贝,线程对变量所有操作,读取,赋值,都必须在工作内存中进行,不能直接写主内存变量,线程间变量值的传递均需要主内存来完成。

免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。

标签: java内存