自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深刻理解Java并發(fā)中的有序性問題和解決之道

開發(fā) 前端
JAVA并發(fā)中的有序性問題其實(shí)比較難理解,本文通過一個例子驗(yàn)證了并發(fā)情況下會出現(xiàn)有序性的問題,從而引發(fā)意想不到的結(jié)果。這個主要的原因是為了提高性能,指令會發(fā)生重排序?qū)е碌?。為了解決這樣的問題,我們可以使用volatile這個關(guān)鍵字修飾變量,它能夠保證有序性和可見性,但是無法保證原子性。

?問題

Java并發(fā)情況下總是會遇到各種意向不到的問題,比如下面的代碼:

int num = 0;

boolean ready = false;
// 線程1 執(zhí)行此方法
public void actor1(I_Result r) {
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}
// 線程2 執(zhí)行此方法
public void actor2(I_Result r) {
num = 2;
ready = true;
}
  • 線程1中如果發(fā)現(xiàn)ready=true?,那么r1的值等于num + num?,否則等于1,然后將結(jié)果保存到I_Result對象中
  • 線程2中先修改num=2?,然后設(shè)置ready=true

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

  • r1值等于4, 這個大家都能想到, CPU先執(zhí)行了線程2,然后執(zhí)行線程1
  • r1值等于1,這個也容易理解,CPU先執(zhí)行了線程1,然后執(zhí)行線程2
  • 那我如果說r1值有可能等于0,大家可能覺得離譜,不信的話,我們驗(yàn)證下。

壓測驗(yàn)證結(jié)果

由于并發(fā)問題出現(xiàn)的概率比較低,我們可以使用openjdk?提供的jcstress框架進(jìn)行壓測,就能夠出現(xiàn)各種可能的情況。

jcstress:全名The Java Concurrency Stress tests,是一個實(shí)驗(yàn)工具和一套測試工具,用于幫助研究JVM、類庫和硬件中并發(fā)支持的正確性。詳細(xì)使用可以參考文章:https://www.cnblogs.com/wwjj4811/p/14310930.html

1.生成壓測工程


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

圖片

生成的工程代碼如下圖:

圖片

2.填充測試內(nèi)容

圖片

  • 方法actor1?是壓測第一個線程干的活,將結(jié)果保存到I_Result中。
  • 方法actor2是壓測第二個線程干的活
  • 類前面的@Outcome?注解用來展示驗(yàn)證結(jié)果,特別是id="0"這個是我們感興趣的結(jié)果

3.運(yùn)行壓測工程

mvn clean install java -jar target/jcstress.jar

4.查看運(yùn)行結(jié)果

運(yùn)行結(jié)果如下圖所示:

圖片

  • 有4000多次出現(xiàn)了0的結(jié)果
  • 大部分情況的結(jié)果還是1和4

你是不是還是很困惑,其實(shí)這就是并發(fā)執(zhí)行的一些坑,我們下面來解釋下原因。

原因分析

如果先要出現(xiàn)r1的值等于0?,那么有一個可能0+0=0?,那么也就是num=0。

你可能想num怎么可能等于0,代碼邏輯明明是先設(shè)置num=2?,然后才修改ready=true?, 最后才會走到num+num 的邏輯啊....

在并發(fā)的世界里,我們千萬不要被固有的思維限制了,那是不是有可能num=2和ready=true的執(zhí)行順序發(fā)生了變化呢。如果你想到這里,也基本接近真相了。

原因: JAVA中在指令不存在依賴的情況下,會進(jìn)行順序的調(diào)整,這種現(xiàn)象叫做指令重排序,是 JIT 編譯器在運(yùn)行時的一些優(yōu)化。這也是為什么出現(xiàn)0的根本原因。

指令重排不會影響單線程執(zhí)行的結(jié)果,但是在多線程的情況下,會有個可能出現(xiàn)問題。

理解指令重排序

前面提到出現(xiàn)問題的原因是因?yàn)橹噶钪嘏判颍憧赡苓€是不大理解指令重排序究竟是什么,以及它的作用,那我這邊用一個魚罐頭的故事帶大家理解下。

我們可以把工人當(dāng)做CPU,魚當(dāng)做指令,工人加工一條魚需要 50 分鐘,如果一條魚、一條魚順序加工,這樣是不是比較慢?

圖片

沒辦法得優(yōu)化下,不然要喝西北風(fēng)了,發(fā)現(xiàn)每個魚罐頭的加工流程有 5 個步驟:

  • 去鱗清洗 10分鐘
  • 蒸煮瀝水 10分鐘
  • 加注湯料 10分鐘
  • 殺菌出鍋 10分鐘
  • 真空封罐 10分鐘

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

圖片

我們發(fā)現(xiàn)中間用很多步驟是并行做的,大大的提高了效率。但是在并行加工魚的過程中,就會出現(xiàn)順序的調(diào)整,比如先做第二條的魚的某個步驟,然后在做第一條魚的步驟。

現(xiàn)代 CPU 支持多級指令流水線,幾乎所有的馮?諾伊曼型計算機(jī)的 CPU,其工作都可以分為 5 個階段:取指令、指令譯碼、執(zhí)行指令、訪存取數(shù)和結(jié)果寫回,可以稱之為五級指令流水線。CPU 可以在一個時鐘周期內(nèi),同時運(yùn)行五條指令的不同階段(每個線程不同的階段),本質(zhì)上流水線技術(shù)并不能縮短單條指令的執(zhí)行時間,但變相地提高了指令地吞吐率。

圖片

處理器在進(jìn)行重排序時,必須要考慮指令之間的數(shù)據(jù)依賴性

單線程環(huán)境也存在指令重排,由于存在依賴性,最終執(zhí)行結(jié)果和代碼順序的結(jié)果一致

多線程環(huán)境中線程交替執(zhí)行,由于編譯器優(yōu)化重排,會獲取其他線程處在不同階段的指令同時執(zhí)行

volatile關(guān)鍵字

那么對于上面的問題,如何解決呢?

使用volatile關(guān)鍵字。

圖片

volatile? 的底層實(shí)現(xiàn)原理是內(nèi)存屏障,Memory Barrier(Memory Fence)

  • 對volatile 變量的寫指令后會加入寫屏障
  • 對volatile 變量的讀指令前會加入讀屏障

內(nèi)存屏障本質(zhì)上是一個CPU指令,形象點(diǎn)理解就是一個柵欄,攔在那里,無法跨越。

內(nèi)存屏障分為寫屏障和讀屏障,有什么有呢?

1.保證可見性

  • 寫屏障保證在該屏障之前的,對共享變量的改動,都同步到主存當(dāng)中
  • 讀屏障保證在該屏障之后,對共享變量的讀取,加載的是主存中最新數(shù)據(jù)

2.保證有序性

寫屏障會確保指令重排序時,不會將寫屏障之前的代碼排在寫屏障之后

讀屏障會確保指令重排序時,不會將讀屏障之后的代碼排在讀屏障之前

圖片

回到前面的問題,如果對ready?加了volatile以后,那么num=2就無法到后面去了,同樣讀取也是,如上圖所示。

final底層也是通過內(nèi)存屏障實(shí)現(xiàn)的,它與volatile一樣。

  • 對final變量的寫指令加入寫屏障。也就是類初始化的賦值的時候會加上寫屏障。
  • 對final變量的讀指令加入讀屏障。加載內(nèi)存中final變量的最新值。

總結(jié)

JAVA并發(fā)中的有序性問題其實(shí)比較難理解,本文通過一個例子驗(yàn)證了并發(fā)情況下會出現(xiàn)有序性的問題,從而引發(fā)意想不到的結(jié)果。這個主要的原因是為了提高性能,指令會發(fā)生重排序?qū)е碌?。為了解決這樣的問題,我們可以使用volatile這個關(guān)鍵字修飾變量,它能夠保證有序性和可見性,但是無法保證原子性。如果以后遇到一些成員變量或者靜態(tài)變量就要特別注意了,需要分析并發(fā)情況下會有哪些問題。

責(zé)任編輯:武曉燕 來源: JAVA旭陽
相關(guān)推薦

2024-02-27 17:46:25

并發(fā)程序CPU

2017-01-13 08:52:46

HDFS機(jī)制Then

2024-06-24 08:31:42

2016-11-10 18:57:19

雙十一高并發(fā)

2012-12-31 14:59:58

Android開發(fā)Layout_weig

2024-05-21 08:44:43

MySQLB+Tree內(nèi)存

2011-04-18 19:36:10

HSRP協(xié)議

2011-05-18 09:47:39

spring

2009-10-10 15:26:11

資產(chǎn)管理

2022-01-14 08:08:11

Java依賴沖突

2011-03-14 13:11:07

Oracle數(shù)據(jù)庫

2009-04-08 10:31:01

2016-11-03 08:57:02

javascriptjquerynode.js

2018-06-08 10:45:18

云計算成本評估應(yīng)用程序

2019-11-27 10:28:11

公共安全大數(shù)據(jù)數(shù)據(jù)聯(lián)系

2020-03-04 08:25:18

有序性并發(fā)結(jié)構(gòu)

2016-12-22 09:02:35

Linux誤刪文件

2021-03-28 21:33:07

Redis熱點(diǎn)key

2020-09-20 22:14:14

編程PythonJava

2015-07-16 23:10:19

dynatrace應(yīng)用性能管理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號