赞
踩
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可以在一个时间内,执行五条指令的不同阶段。从而提升指令的吞吐率。
- package com.bo.threadstudy.five;
-
- import org.openjdk.jcstress.annotations.*;
- import org.openjdk.jcstress.infra.results.I_Result;
-
- /**
- * 指令重排
- */
- @JCStressTest
- @Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "ok")
- @Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "!!!!")
- @State
- public class InstructionRearrangementTest03 {
- int num = 0;
-
- boolean ready = false;
-
- @Actor
- public void actor1(I_Result r) {
- if(ready) {
- r.r1 = num + num;
- } else {
- r.r1 = 1;
- }
- }
- @Actor
- public void actor2(I_Result r) {
- num = 2;
- ready = true;
- }
-
-
- }

启动方式按照这个,【多线程&高并发】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)保证在该屏障之后的,对共享变量的读取,都是读取的主存中的最新内容。
写屏障保证指令重排序时,不会将写屏障之前的代码重排序到写屏障后方。
读屏障保证指令重排序时,不会讲读屏障之后的代码重排序到读屏障前方。
写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
而有序性的保证也只是保证了本线程内相关代码不被重排序,或者说只能保证写屏障之前,读屏障之后的代码不会重排序。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。