首页IT科技java实现并发的机制(深刻理解JAVA并发中的有序性问题和解决之道)

java实现并发的机制(深刻理解JAVA并发中的有序性问题和解决之道)

时间2025-04-30 02:06:10分类IT科技浏览3751
导读:问题 Java并发情况下总是会遇到各种意向不到的问题,比如下面的代码:...

问题

Java并发情况下总是会遇到各种意向不到的问题           ,比如下面的代码:

int num = 0; boolean ready = false; // 线程1 执行此方法 public void actor1(I_Result r) { if(ready) { r.r1 = num + num; } else { r.r1 = 1; } } // 线程2 执行此方法 public void actor2(I_Result r) { num = 2; ready = true; } 线程1中如果发现ready=true,那么r1的值等于num + num                 ,否则等于1    ,然后将结果保存到I_Result对象中 线程2中先修改num=2,然后设置ready=true

那大家觉得I_Result中的r1值可能是多少呢?

r1值等于4        , 这个大家都能想到, CPU先执行了线程2                  ,然后执行线程1 r1值等于1       ,这个也容易理解     ,CPU先执行了线程1                  ,然后执行线程2 那我如果说r1值有可能等于0          ,大家可能觉得离谱  ,不信的话                 ,我们验证下           。

压测验证结果

由于并发问题出现的概率比较低             ,我们可以使用openjdk提供的jcstress框架进行压测,就能够出现各种可能的情况                 。

jcstress:全名The Java Concurrency Stress tests              ,是一个实验工具和一套测试工具                ,用于帮助研究JVM           、类库和硬件中并发支持的正确性    。详细使用可以参考文章:https://www.cnblogs.com/wwjj4811/p/14310930.html

生成压测工程 mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jcstress -DarchetypeArtifactId=jcstress-java-test-archetype -DarchetypeVersion=0.5 -DgroupId=com.alvin -DartifactId=juc-order -Dversion=1.0

生成的工程代码如下图:

填充测试内容 方法actor1是压测第一个线程干的活  ,将结果保存到I_Result中        。 方法actor2是压测第二个线程干的活 类前面的@Outcome注解用来展示验证结果           ,特别是id="0"这个是我们感兴趣的结果 运行压测工程 mvn clean install java -jar target/jcstress.jar 查看运行结果

运行结果如下图所示:

有4000多次出现了0的结果 大部分情况的结果还是1和4

你是不是还是很困惑                 ,其实这就是并发执行的一些坑    ,我们下面来解释下原因                  。

原因分析

如果先要出现r1的值等于0        ,那么有一个可能0+0=0                  ,那么也就是num=0       。

你可能想num怎么可能等于0       ,代码逻辑明明是先设置num=2,然后才修改ready=true, 最后才会走到num+num 的逻辑啊....

在并发的世界里     ,我们千万不要被固有的思维限制了                  ,那是不是有可能num=2和ready=true的执行顺序发生了变化呢     。如果你想到这里          ,也基本接近真相了                  。

原因: JAVA中在指令不存在依赖的情况下  ,会进行顺序的调整                 ,这种现象叫做指令重排序             ,是 JIT 编译器在运行时的一些优化          。这也是为什么出现0的根本原因  。

指令重排不会影响单线程执行的结果,但是在多线程的情况下              ,会有个可能出现问题                 。

理解指令重排序

前面提到出现问题的原因是因为指令重排序                ,你可能还是不大理解指令重排序究竟是什么  ,以及它的作用           ,那我这边用一个鱼罐头的故事带大家理解下             。

我们可以把工人当做CPU                 ,鱼当做指令    ,工人加工一条鱼需要 50 分钟        ,如果一条鱼                 、一条鱼顺序加工                  ,这样是不是比较慢?

没办法得优化下       ,不然要喝西北风了     ,发现每个鱼罐头的加工流程有 5 个步骤:

去鳞清洗 10分钟 蒸煮沥水 10分钟 加注汤料 10分钟 杀菌出锅 10分钟 真空封罐 10分钟

每个步骤中也是用到不同的工具                  ,那能否可以并行呢?如下图所示:

我们发现中间用很多步骤是并行做的          ,大大的提高了效率。但是在并行加工鱼的过程中  ,就会出现顺序的调整                 ,比如先做第二条的鱼的某个步骤             ,然后在做第一条鱼的步骤              。

现代 CPU 支持多级指令流水线,几乎所有的冯•诺伊曼型计算机的 CPU              ,其工作都可以分为 5 个阶段:取指令    、指令译码        、执行指令                  、访存取数和结果写回                ,可以称之为五级指令流水线                。CPU 可以在一个时钟周期内  ,同时运行五条指令的不同阶段(每个线程不同的阶段)           ,本质上流水线技术并不能缩短单条指令的执行时间                 ,但变相地提高了指令地吞吐率  。

处理器在进行重排序时    ,必须要考虑指令之间的数据依赖性

单线程环境也存在指令重排        ,由于存在依赖性                  ,最终执行结果和代码顺序的结果一致 多线程环境中线程交替执行       ,由于编译器优化重排     ,会获取其他线程处在不同阶段的指令同时执行

volatile关键字

那么对于上面的问题                  ,如何解决呢?

使用volatile关键字           。

volatile 的底层实现原理是内存屏障          ,Memory Barrier(Memory Fence)

对 volatile 变量的写指令后会加入写屏障 对 volatile 变量的读指令前会加入读屏障

内存屏障本质上是一个CPU指令  ,形象点理解就是一个栅栏                 ,拦在那里             ,无法跨越                 。

内存屏障分为写屏障和读屏障,有什么有呢?

保证可见性 写屏障保证在该屏障之前的              ,对共享变量的改动                ,都同步到主存当中 读屏障保证在该屏障之后  ,对共享变量的读取           ,加载的是主存中最新数据 保证有序性 写屏障会确保指令重排序时                 ,不会将写屏障之前的代码排在写屏障之后 读屏障会确保指令重排序时    ,不会将读屏障之后的代码排在读屏障之前

回到前面的问题        ,如果对ready加了volatile以后                  ,那么num=2就无法到后面去了       ,同样读取也是     ,如上图所示    。

final底层也是通过内存屏障实现的                  ,它与volatile一样        。

对final变量的写指令加入写屏障                  。也就是类初始化的赋值的时候会加上写屏障       。 对final变量的读指令加入读屏障     。加载内存中final变量的最新值                  。

总结

JAVA并发中的有序性问题其实比较难理解          ,本文通过一个例子验证了并发情况下会出现有序性的问题  ,从而引发意想不到的结果          。这个主要的原因是为了提高性能                 ,指令会发生重排序导致的  。为了解决这样的问题             ,我们可以使用volatile这个关键字修饰变量,它能够保证有序性和可见性              ,但是无法保证原子性                 。如果以后遇到一些成员变量或者静态变量就要特别注意了                ,需要分析并发情况下会有哪些问题             。

如果本文对你有帮助的话  ,请留下一个赞吧

更多技术干活和学习资料尽在个人公众号——JAVA旭阳

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
提升网站排名的10种SEO优化策略(从研究到外部链接,让你的网站在搜索引擎中更容易被发现) nt7 hdd installer(NT6 HDD Installer安装器怎么安装win10专业版)