当前位置:网站首页>指令重排以及案例
指令重排以及案例
2022-08-03 14:58:00 【兀坐晴窗独饮茶】
基本概念
JVM 会在不影响正确性的前提下,可以调整语句的执行顺序,思考下面一段代码
static int i;
static int j;
// 在某个线程内执行如下赋值操作
i = ...;
j = ...;
比如 : 调整为 下面的 , 最终的结果也不会发生变化
j = ...
i = ...
这种特性称之为『指令重排』,多线程下『指令重排』会影响正确性。
指令重排序优化原理
事实上,现代处理器会设计为一个时钟周期完成一条执行时间最长的 CPU 指令。为什么这么做呢?可以想到指令 还可以再划分成一个个更小的阶段
例如,每条指令都可以分为:
- 取指令
- 指令译码
- 执行指令
- 内存访问
- 数据写回
这 5 个阶段
术语参考:
instruction fetch (IF)
instruction decode (ID)
execute (EX)
memory access (MEM)
register write back (WB)
在不改变程序结果的前提下,这些指令的各个阶段可以通过重排序和组合来实现指令级并行,这一技术在 80’s 中 叶到 90’s 中叶占据了计算架构的重要地位。
分阶段,分工是提升效率的关键! , 指令重排的前提是,重排指令不能影响结果,例如
// 可以重排的例子
int a = 10; // 指令1
int b = 20; // 指令2
System.out.println( a + b );
// 不能重排的例子
int a = 10; // 指令1
int b = a - 5; // 指令2
指令重排案例
简单案例
package cn.knightzz.instructions;
import lombok.extern.slf4j.Slf4j;
/** * @author 王天赐 * @title: TestDemo01 * @projectName hm-juc-codes * @description: 诡异的情况 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a> * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a> * @create: 2022-08-03 07:07 */
@SuppressWarnings("all")
@Slf4j(topic = "c.TestDemo01")
public class TestDemo01 {
boolean ready = false;
int num = 0;
public void actor01(I_Result r) {
if (ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}
public void actor02() {
num = 2;
ready = true;
}
public static void main(String[] args) throws InterruptedException {
I_Result r = new I_Result();
TestDemo01 demo01 = new TestDemo01();
Thread t1 = new Thread(() -> {
demo01.actor01(r);
demo01.actor02();
}, "t1");
Thread t2 = new Thread(() -> {
demo01.actor01(r);
demo01.actor02();
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("r.r1 = " + r.r1);
}
}
@SuppressWarnings("all")
class I_Result {
int r1 = 0;
}
以上的代码可能会出现的情况:
- 线程1 先执行,这时 ready = false,所以进入 else 分支结果为 1
- 线程2 先执行 num=2,但没来得及执行ready = true,线程1执行,还是进入else分支,结果为1
- 线程2 执行到 ready = true,线程1 执行,这回进入 if 分支,结果为 4(因为 num 已经执行过了)
结果为0的情况 :
线程2开始执行, 由于 num = 2; ready = true
二者指令上没有依赖, 所以可以重排, JVM会对指令的顺序进行重新排序 :
ready = true;
num = 2;
执行在将 true
赋值给 ready 后, 此时 num = 2
还未执行
此时 线程上下文切换, if ready = ture
, r.r1 = 0 + 0
最终的结果就是0
这种现象叫做指令重排,是 JIT 编译器在运行时的一些优化,这个现象需要通过大量测试才能复现:
借助 java 并发压测工具 jcstress https://wiki.openjdk.java.net/display/CodeTools/jcstress
创建项目 :
GroupId : org.openjdk.jcstress
ArtifactId : jcstress-java-test-archetype
Version : 0.5
生成对应的项目后, 我们就可以添加相应的代码
/* * Copyright (c) 2017, Red Hat Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Oracle nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */
package cn.knightzz;
import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.II_Result;
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 ConcurrencyTest {
int num = 0;
volatile 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;
}
}
然后使用mvn对代码进行打包
执行生成的 jar 包 java -jar jcstress.jar -v
运行结果
可以看到上面的结果, 指令重排出现的概率很低, 我是没试出来
解决办法
解决办法 : 使用 volatile
修饰对应的变量 可以禁用指令重排
volatile boolean ready = false;
边栏推荐
猜你喜欢
eolink告诉你,国内Api行业,可以内卷到什么程度?
MMA安装及使用优化
【指针内功修炼】函数指针 + 函数指针数组 + 回调函数(二)
SQL 不新增表 把一张表定义成两张
6000 字+,帮你搞懂互联网架构演变历程!
兔起鹘落全端涵盖,Go lang1.18入门精炼教程,由白丁入鸿儒,全平台(Sublime 4)Go lang开发环境搭建EP00
进程通信的方式
With 1000 lines of code statistics after the xi 'an housing prices, I have a startling discovery...
你把 浏览器滚动事件 玩明白
【实战】Next.js + 云函数开发一个面试刷题网站
随机推荐
2021年12月电子学会图形化四级编程题解析含答案:聪明的小猫
A high-performance creation book, ASUS Dreadnought Pro15 2022 is completely enough for daily photo editing and editing!
【FPGA教程案例44】图像案例4——基于FPGA的图像中值滤波verilog实现,通过MATLAB进行辅助验证
【重构map】【重构filter】【重构Some】【重构reduce方法】【重构flat函数】
php类的析构函数:__destruct
liunx服务器nohup不输出日志文件的方法
币圈提款机:Solana钱包出现未知安全漏洞 大量用户数字资产被盗
【实战】Next.js + 云函数开发一个面试刷题网站
Day1:面试必考真题
Clickhouse Filling the Pit 3: Left Join changed to Right Join, resulting in incorrect statistical results
The difference between servlet and jsp _ the difference between servlet and class
分布式系统与微服务的区别
问题6:下拉框测试点
云硬盘EVS详解以及如何用与避坑【华为云至简致远】
地球自转加快
正则表达式入门一
一个在浏览器中看到的透视Cell实现
STL简介
redis的使用方法
2021年12月电子学会图形化四级编程题解析含答案:棕熊大战