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

让我们一起学习并发编程:Java内存模型(4)挥发性记忆语义语义

时间:2023-03-07 23:51:23 网络应用技术

  理解挥发性特征的好方法之一是读取/编写挥发性变量的单个读数/写作,并看到它与同一锁一起将单个读数/写作操作同步。

  代码示例:

  假设有多个线程要调用上述程序的3种方法,这等同于语义和下面。

  如前两个程序中所示,挥发性变量的单个读取和写作操作与与普通变量读取和写入操作相同的锁定,并且它们之间的执行效果相同。

  上述代码的摘要:

  总结挥发性功能:

  对于程序员,我们需要更加注意挥发性线程内存的可见性。

  从JDK1.5(JSR-133)开始,挥发性变量的写入阅读可以实现线程之间的通信。

  代码示例:

  假设A线程A执行writer()方法,则线程B执行reader()方法。根据规则,符合hapens-before,此过程中建立的关系之前的关系如下:

  以上关系以前的关系:

  摘要:在此处的线程A编写挥发性变量之后,线程B读取相同的挥发性变量。在编写挥发性变量之前,所有可见的共享变量都可以看到A Things A。在读取螺纹B中相同的挥发性变量后,将立即看到线程B。

  当挥发性写入挥发性变量时,JMM将在与主内存的主内存相对应的本地内存中刷新共享变量值。

  以上挥发性示例是一个示例。假设该线程A第一个执行writer()方法,然后线程B执行reader()方法。一开始,最初是两个线程的本地内存中的标志和a。

  a执行挥发性后,共享变量状态示意图。

  线程A编写标志变量后,将局部内存A中线程A更新的两个共享变量的值刷新到主内存中。目前,A的本地内存与主内存中的值一致。

  当挥发性读取挥发性变量时,JMM将使本地内存对应于线程无效。该线程将从主内存中读取共享变量。

  b执行挥发性后,执行共享变量的状态图。

  读取标志变量后,本地内存B中包含的值已将其放置为无效。这次,线程B必须从主内存中读取共享变量。线程B的读取操作将导致本地内存B的值to to to主内存中共享变量的值。

  总结挥发性的写作和挥发性读取的记忆语义

  编程序列分为编译器进行分类和处理器(我以前的博客文章的内容是编写的)。为了实现挥发性的记忆语义,JMM将禁止这两种类型的重型分类。

  挥发性重型排放规则表

  第二个操作以挥发性编写,无法对编译器进行排序。

  上面的摘要:

  为了实现挥发性的内存语义,编译器将在生成字节代码以禁止特定类型的处理器排序时将内存屏障插入指令序列中。

  JMM采用了保守的策略记忆障碍插入策略,如下:

  保守的策略可以确保可以在任何处理器平台中获得正确的挥发性内存语义。

  在保守策略下,挥发性写入插入记忆屏障后产生的指令序列图:

  解释:

  Storestore障碍可以保证在写下挥发性之前,所有处理器都可以看到所有以前的普通写作操作。这是因为Storestore屏障将保证所有普通的将军都写在挥发性之前的主要记忆中。

  在保守策略下,挥发性读取插入内存屏障后生成的指令序列图:

  解释:

  负载屏障用于禁止处理器通过一般阅读的一般阅读来读取上述挥发性。负载店条用于禁止处理器并对上述挥发性进行排序以读取上述一般写作。

  上面阅读的挥发性和挥发性的内存屏障插入策略是非常保守的。在实际执行中,只要它不会改变挥发性写作阅读的记忆语义,则编译器可以根据特定情况省略不必要的障碍。

  代码示例:

  对于volatilebarrierexample的ReadAndWrite(),可以在生成字节码生成字节代码时优化编译器:

  注意:无法省略最终的Storeload屏障。由于编写了第二个波动性,因此程序返回。这次,编译器无法准确确定以后是否会有挥发性的读写操作。出于安全原因,编译器通常在此处插入Storeload屏障。

  以上优化可以在任何处理器平台上进行定位,但是由于不同的处理器具有不同的“松散”处理器内存模型,因此根据特定处理器内存模型,还可以继续优化内存屏障的插入。

  X86处理器平台优化

  X86处理器只能对写入阅读操作进行排序。x86不会通过阅读,阅读,写作,写作,写作写作来对其进行排序,因此X86处理器将省略与这三个操作类型相对应的内存屏障。X86平台,JMM仅需要在挥发性写入挥发性后才能插入一个Storeload屏障以正确实现挥发性以读取内存语义。在同一时间,这意味着在X86处理器中,挥发性的开销将远远大于阅读的开销。

  功能:

  就可伸缩性和执行性能而言:

  本文总结了“ Java并行编程艺术”。下一篇文章总结了“锁的语义语义”,因此请继续关注。