线程学习(21)-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可以在一个时间内,执行五条指令的不同阶段。从而提升指令的吞吐率。

举个例子

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)保证在该屏障之后的,对共享变量的读取,都是读取的主存中的最新内容。

如何保证有序性

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

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

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值