当前位置:   article > 正文

线程学习(21)-volatile有序性_volatile 有序性

volatile 有序性

volatile保证了有序性。这里的有序性,不是前面所说的指令交错的问题,而是指令中更底层的东西。

指令重排

JVM在不影响正确性的情况下,会对语句的执行顺序来进行重排。为了提高指令执行的效率。

名词

Clock Cycle Time
主频的概念大家接触的比较多,而 CPU 的 Clock Cycle Time(时钟周期时间),等于主频的倒数,意思是 CPU 能够识别的最小时间单位,比如说 4G 主频的 CPU 的 Clock Cycle Time 就是 0.25 ns,作为对比,我们墙上挂钟的Cycle Time 是 1s
例如,运行一条加法指令一般需要一个时钟周期时间


CPI
有的指令需要更多的时钟周期时间,所以引出了 CPI (Cycles Per Instruction)指令平均时钟周期数,即平均一个指令需要执行的时间周期是多少。


IPC
IPC(Instruction Per Clock Cycle) 即 CPI 的倒数,表示每个时钟周期能够运行的指令数


CPU 执行时间

程序的 CPU 执行时间,即我们前面提到的 user + system 时间,可以用下面的公式来表示
程序 CPU 执行时间 = 指令数 * CPI * Clock Cycle Time

指令重排序优化

在java层面,我们看到的最小层级是javap锁编译出来的指令。但程序在执行过程中,更底层,一个指令可以拆分成更小的阶段,比如,划分为五个阶段:取指令,指令译码,执行指令,内存访问,数据写回。

在不改变程序的执行结果的情况下,这些指令可以发生重排序与组合来实现指令集并行现象,进而提升执行效率。

只有在不影响结果的情况下,可以指令重排。

// 可以重排的例子
int a = 10; // 指令1
int b = 20; // 指令2
System.out.println( a + b );
// 不能重排的例子
int a = 10; // 指令1
int b = a - 5; // 指令
 

现代CPU实现多级指令流水线操作,例如同时执行取指令,指令译码,执行指令,内存访问,数据写回这五个步骤的,就叫做五级指令流水线。CPU可以在一个时间内,执行五条指令的不同阶段。从而提升指令的吞吐率。

举个例子

  1. package com.bo.threadstudy.five;
  2. import org.openjdk.jcstress.annotations.*;
  3. import org.openjdk.jcstress.infra.results.I_Result;
  4. /**
  5. * 指令重排
  6. */
  7. @JCStressTest
  8. @Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "ok")
  9. @Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "!!!!")
  10. @State
  11. public class InstructionRearrangementTest03 {
  12. int num = 0;
  13. boolean ready = false;
  14. @Actor
  15. public void actor1(I_Result r) {
  16. if(ready) {
  17. r.r1 = num + num;
  18. } else {
  19. r.r1 = 1;
  20. }
  21. }
  22. @Actor
  23. public void actor2(I_Result r) {
  24. num = 2;
  25. ready = true;
  26. }
  27. }

启动方式按照这个,【多线程&高并发】jcstress并发测试工具使用教程详解_漫话人生的博客-CSDN博客_jcstress,直接run运行,不需要专门找文件,看了下在高并发下的结果。

在并发情况下,确实会主线结果为0的场景,虽然概率只有两千分之一。原因的话,就是actor2方法在执行过程中,进行了指令重排,将ready=true以及num=2来进行顺序的替换,此时先将ready置为true,然后切换至actor1线程,此时num=0,而ready=true,得到的结构就是0。指令重排。

禁用指令重排

 这里应该采用volatile。按照写屏障的规则,会将当前方法中的上方其它变量一并写入至主存中,所以这里对ready进行volatile修饰,保证指令重排不会影响。

volatile boolean ready = false;

当然,如果想看更详细的信息,可以去index.html里去看。

 假设我不知道预期结果的情况下,这个JCStress是否可以把所有在这个过程中出现的所有结果都打印出来呢,应该有这个功能,后期有时间看看。

如何保证可见性

写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都会写入至主存中。

读屏障(ifence)保证在该屏障之后的,对共享变量的读取,都是读取的主存中的最新内容。

如何保证有序性

 写屏障保证指令重排序时,不会将写屏障之前的代码重排序到写屏障后方。

读屏障保证指令重排序时,不会讲读屏障之后的代码重排序到读屏障前方。

写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
而有序性的保证也只是保证了本线程内相关代码不被重排序,或者说只能保证写屏障之前,读屏障之后的代码不会重排序。
 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/920482
推荐阅读
相关标签
  

闽ICP备14008679号