当前位置: 首页 > 网络应用技术

让我们一起学习并发编程:Java内存模型(1)基础

时间:2023-03-06 19:07:32 网络应用技术

  介绍:

  Java线程之间的通信对程序员完全透明,并且内存可见性问题很容易困扰Java程序员。这一系列文章将揭示Java记忆模型的奥秘。该系列文章大致分为4个部分,即:

  需要在并发编程中处理两个关键问题:如何同步如何在线程和线程之间进行通信(这里的线程是指同时执行的活动实体)。

  通信 - 线程之间的交换信息之间的哪种机制。在命令编程中,线程之间有两种通信机制:共享内存和消息传输。

  同步 - 程序中的程序,以控制不同线程 - 键操作的相对顺序。

  总结:

  并发Java在共享内存模型中使用。Java线程之间的通信始终是隐式的。整个通信过程与程序员完全透明。工作机制可能会遇到各种奇怪的记忆可见性问题。

  Java中的所有实例域,静态域和数组元素都存储在堆内存中,并在堆中的线程之间共享(“共享变量”(“共享变量”请参阅文章中的文章)。局部变量,方法定义参数和异常处理器参数(异常处理程序参数)将不会在线程之间共享。

  Java线程之间的通信由Java内存模型(JMM)控制。JMM确定何时可以看到一个线程到另一个线程。从一个抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中。每个线程都有一个私有的本地内存(本地内存),在本地内存中存储了线程以读取/编写共享变量的副本。本地内存期间JMM的抽象概念实际上并不存在。JMM涵盖了缓存的优化,编写缓冲区,注册和其他硬件和编译器。

  数字:

  Java内存模型的摘要示意图

  从上图,如果螺纹A和线程B需要通信,则必须经过以下两个步骤。

  数字:

  线程之间的通信图

  如上图所示,本地内存A和本地内存B在主内存中具有变量X的副本。请确保在开始时,这三个内存中的X值为0。执行线程A时,更新的X(假设值为1)的值临时存储在其本地内存中。目前,主内存中的x值变为1。然后,线程B转到主内存以读取以读取在主内存中。目前,线程B的本地内存X的值也更新为1。

  总体而言,这两个步骤本质上是线程a以将消息发送到线程B,并且此通信过程必须通过主内存。JMM通过控制主内存和本地内存之间的相互作用来为Java程序员提供可见的内存保证每个线程。

  执行程序时,为了提高性能,编译器和处理器通常对说明进行分类。重订单分为三种类型:

  Java源代码的最终实际执行的指令顺序将进行以下三种重型排序,其中1个属于编译器的重分类,而2和3属于处理器进行分类。

  源代码到最终执行指令序列示意图

  测序可能会导致多线程程序的内存可见性问题。对于编译器,JMM编译器排序规则将禁止特定类型的编译器分类(并非所有编译器都需要禁止所有编译器)。对于处理器的回归分类,JMM处理器的调节规则将要求Java编译器插入特定类型在生成指令序列时的内存屏障(内存围栏)指令以及内存屏障指令是从内存屏障指令中的特定类型禁止的。处理器专注于分类。

  JMM是一种语言 - 级记忆模型。它可以通过禁止特定类型的编译器对处理器进行分类和处理,从而确保在不同的编译器和不同的处理器平台上,为程序员提供一致的内存可见性保证。

  1.4.1编写缓冲器现代处理器将使用数据暂时存储在记忆中的数据。编写缓冲区的主要作用:

  普通处理器允许的重分类类型(Y-表示允许两个操作分类,n指示处理器不允许对两个操作进行排序)

  sparc-tsonnnynx86nnnnnnynia64yyyyynpoperpcyyynnyn注意:通用处理器允许商店负载排序;不允许常见处理器重新分配该数据依赖性操作。N更多表示处理器具有相对强大的处理器存储器模型。

  由于写入缓冲区仅在其所在的处理器上可见,因此此功能将对内存操作的执行顺序产生非常重要的影响:处理器的处理器的处理/写作操作不一定与记忆的实际阅读。/写作顺序是一致的。

  例如:

  伪代码a = 1;// a1x = b;// a2b = 2;// b1y = a;// B2可能会运行结果初始状态:A = B = 0; = 0;假设处理器A和处理器B按程序顺序并行执行内存访问,则可以在最后获得X = Y = 0的结果。具体原因如下:

  处理器和内存互动

  注意:处理器A和处理器B可以同时将共享变量写入其自己的写作缓冲区(A1,B1),然后从内存中读取另一个共享变量(A2,B2),最后写下自己写作保存在内存中的缓冲灰烬肮脏的数据被刷新到内存(A3,b3)。执行此计时后,程序可以获得x = y = 0结果。

  1.4.2为了确保内存的可见性,Java编译器将在指令序列的适当位置插入内存屏障指令,以禁止特定类型的处理器。JMM将内存屏障指令分为4个类别:

  负载屏障1;负载;LOAD2确保LOAD1数据的负载是在LOAD2和所有后续加载指令之前加载的。斯托里斯托尔;Store2确保store1数据可以首先在Store2和所有后续存储中看到其他处理器(刷新到主内存)。付费加载2和所有后续加载指令。StorEloAD障碍将启用所有内存访问指令(存储和加载指令,)在屏障之前进行障碍之前,在屏障之后进行内存访问说明。Storeload屏障是“全面的障碍”,它具有其他三个障碍的作用。大多数现代处理器都支持障碍物(其他类型的障碍不一定是其他类型的障碍物由所有处理器支持)。此障碍开销的执行昂贵,因为处理器需要将缓冲区的内容刷新为内存(缓冲区冲洗)。

  从JDK1.5开始,Java使用新的JSR-133内存模型。JSR-133使用发生之前的概念来解释操作之间的操作的可见性。另一个,然后在两个操作之间必须存在Hapens -Fore -Fore关系。这里的两个操作可以是单线线程或多线程。

  以前规则:

  注意:

  两项操作之间存在hapens的关系,这并不意味着必须在后一个操作之前执行先前的操作!此前仅需要先前的操作(执行结果)才能查看后者的操作,并且先前的操作在第二个操作之前进行排名(第一个操作是可见的,并订购了第二个操作)。

  与JMM之前发生的关系:

  在发生之前和JMM之间的关系

  一条规则对应于一个或多个编译器,将处理器集中在排序规则上。对于Java程序员而言,发生的规则很简单易于理解。它避免了Java程序员学习复杂的排序规则和这些规则的特定实现方法,以了解JMM提供的内存可见性。

  文章总结了“ Java并行编程艺术”,下一篇文章总结了“重分类”,因此请继续关注。