日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
為什么會(huì)有重排序?和 happens-before 有啥關(guān)系

舉個(gè)例子

成都創(chuàng)新互聯(lián)公司主營(yíng)鼓樓網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,App定制開(kāi)發(fā),鼓樓h5小程序設(shè)計(jì)搭建,鼓樓網(wǎng)站營(yíng)銷推廣歡迎鼓樓等地區(qū)企業(yè)咨詢

在講重排序之前,先來(lái)看一個(gè)例子:

 
 
 
  1. int a = 0, b = 0; 
  2. public void methodOne(){ 
  3.     int one = a; 
  4.     b = 1; 
  5. public void methodTwo(){ 
  6.     int two = b; 
  7.     a = 2; 

應(yīng)該不難看出,在上面的例子中,我定義了兩個(gè)共享變量 a 和 b ,以及兩個(gè)方法。其中第一個(gè)方法是將局部變量 one 賦值為 a ,然后將 b 的值置為 1 。第二個(gè)方法則是將局部變量 two 賦值為 b ,然后將 a 的值置為 2 。

那么我在這里有個(gè)問(wèn)題, ( one , two ) 的值會(huì)是什么?

你可能會(huì)不假思索的告訴我,不是 ( 0 , 1 ) 就是 ( 2 , 0 ) ,這需要看我的 main 方法先執(zhí)行哪個(gè) method 方法。

不錯(cuò),如果這個(gè)程序跑在了單線程上面,這樣回答一點(diǎn)兒毛病都沒(méi)有。

但是,如果是在多線程環(huán)境下呢?

假設(shè),現(xiàn)在 methodOne 和 methodTwo 分別在兩個(gè)不同的線程上執(zhí)行,此時(shí) Java 虛擬機(jī)在執(zhí)行了任意一個(gè)方法的第一條賦值語(yǔ)句之后就切換線程,這個(gè)時(shí)候的 ( one , two ) 的值可能是 ( 0 , 0 )

看到這兒,有沒(méi)有疑惑?為啥呢,怎么我寫(xiě)的程序好好的,到 Java 虛擬機(jī)這里了,它就給我變了呢?

就是因?yàn)樵趫?zhí)行的過(guò)程中,發(fā)生了重排序。它可能是即時(shí)編譯器的重排序,可能是處理器的亂序執(zhí)行,或者是內(nèi)存系統(tǒng)的重排序。

總之,在程序執(zhí)行過(guò)程中,發(fā)生了重排序,然后得到的結(jié)果可能是 ( 0 , 0 ) 這種情況。

為什么會(huì)重排序

看完上面,你可能會(huì)有疑問(wèn),為什么會(huì)有重排序呢?

我的程序按照我自己的邏輯寫(xiě)下來(lái)好好的沒(méi)啥問(wèn)題, Java 虛擬機(jī)為什么動(dòng)我的程序邏輯?

你想想, CPU ,內(nèi)存這些都是非常寶貴的資源, Java 虛擬機(jī)如果在重排序之后沒(méi)啥效果,肯定也不會(huì)做這種費(fèi)力不討好的事情。

那么,重排序帶來(lái)了什么好處呢?

重排序使得程序的性能得以提高

為了方便理解,我拿生活中的場(chǎng)景來(lái)舉例子。

大早上起來(lái),你會(huì)穿衣服,洗漱,做飯,吃飯對(duì)吧。那么在你起床之后,你是怎么做的呢?你是不是會(huì)在洗漱的時(shí)候,先把飯做上(比如讓蒸蛋機(jī)幫你蒸個(gè)雞蛋),然后呢等你洗漱完畢之后,就可以直接吃早飯了。

你為什么要這樣做呢?還不是為了省時(shí)間,可以多睡那么一分鐘,對(duì)不對(duì)。

同樣的道理, Java 虛擬機(jī)之所以要進(jìn)行重排序就是為了提高程序的性能。你寫(xiě)的程序,簡(jiǎn)簡(jiǎn)單單一行代碼,到底層可能需要使用不同的硬件,比如一個(gè)指令需要同時(shí)使用 CPU 和打印機(jī)設(shè)備,但是此時(shí) CPU 的任務(wù)完成了,打印機(jī)的任務(wù)還沒(méi)完成,這個(gè)時(shí)候怎么辦呢?不讓 CPU 執(zhí)行接下來(lái)的指令嗎?CPU 的時(shí)間那么寶貴,你不讓它工作,確定不是在浪費(fèi)它的生命?

所以為了提高利用率以及程序的性能, Java 虛擬機(jī)會(huì)在你這個(gè)指令還沒(méi)完全執(zhí)行完畢的時(shí)候,就去執(zhí)行另外一個(gè)指令。這就是流水線技術(shù)

流水線最怕的是啥?是我執(zhí)行著命令,執(zhí)行著命令,突然中斷了,恢復(fù)中斷的成本是很大的,所以就要想盡辦法,絞盡腦汁不要讓中斷的情況發(fā)生。

即時(shí)編譯器的重排序,處理器的亂序執(zhí)行,以及內(nèi)存系統(tǒng)的重排序的存在,都是為了減少中斷。

到這里,你是不是對(duì)于 Java 虛擬機(jī)進(jìn)行重排序這一點(diǎn)有了了解?

重排序帶來(lái)的問(wèn)題

回到文章剛開(kāi)始舉的那個(gè)例子,重排序提高了 CPU 的利用率沒(méi)錯(cuò),提高了程序性能沒(méi)錯(cuò),但是我的程序得到的結(jié)果可能是錯(cuò)誤的啊,這是不是就有點(diǎn)兒得不償失了?

因?yàn)橹嘏判蚩梢员WC串行語(yǔ)義一致,但是沒(méi)有義務(wù)保證多線程間的語(yǔ)義也一致

凡是問(wèn)題,都有辦法解決,要是沒(méi)有,那就再想想。

它是怎么解決的呢?這就需要來(lái)說(shuō)說(shuō),順序一致性內(nèi)存模型和 JMM ( Java Memory Model , Java 內(nèi)存模型)

順序一致性內(nèi)存模型與 JMM

要說(shuō)數(shù)據(jù)一致性的話,就要說(shuō)一說(shuō),數(shù)據(jù)競(jìng)爭(zhēng)。

啥是數(shù)據(jù)競(jìng)爭(zhēng)呢?在 Java 內(nèi)存模型規(guī)范中給出了定義:

  • 在一個(gè)線程中寫(xiě)一個(gè)變量
  • 在另外一個(gè)線程中讀同一個(gè)變量
  • 寫(xiě)和讀沒(méi)有通過(guò)同步來(lái)排序

當(dāng)代碼中包含數(shù)據(jù)競(jìng)爭(zhēng)時(shí),程序的執(zhí)行結(jié)果往往會(huì)超出你的想象,比如咱們剛開(kāi)始說(shuō)的那個(gè)例子,得到的結(jié)果可能是 ( 0 , 0 ) 。但是如果一個(gè)多線程程序能夠正確同步的話,那上面的結(jié)果就不會(huì)出現(xiàn)了。

Java 內(nèi)存模型對(duì)于正確同步多線程程序的內(nèi)存一致性做了下面的保證:

如果程序是正確同步的,程序的執(zhí)行也會(huì)具有順序一致性即,程序的執(zhí)行結(jié)果與該程序在順序一致性模型中執(zhí)行的結(jié)果相同

這里面的同步包括了使用 volatile , final , synchronized 等關(guān)鍵字來(lái)實(shí)現(xiàn)多線程下的同步。那也就是說(shuō),如果沒(méi)有正確使用這些同步, JMM 就不會(huì)有內(nèi)存可見(jiàn)性的保證,這就會(huì)導(dǎo)致寫(xiě)的程序出錯(cuò)。

順序一致性內(nèi)存模型是一個(gè)理想狀態(tài)下的理論參考模型,它為程序員提供了特別強(qiáng)的內(nèi)存可見(jiàn)性保證,順序一致性模型有兩大特性:

  • 一個(gè)線程中的所有操作必須按照程序的順序來(lái)執(zhí)行(也就是按照寫(xiě)的代碼的順序來(lái)執(zhí)行)
  • 不管程序是否同步,所有線程都只能看到一個(gè)單一的操作執(zhí)行順序。也就是說(shuō),在順序一致性模型中,每個(gè)操作必須是原子性的,而且立刻對(duì)所有線程都是可見(jiàn)的。

上面說(shuō)了,順序一致性內(nèi)存模型是一個(gè)理想狀態(tài)下的理論參考模型,因?yàn)轫樞蛞恢滦詢?nèi)存模型要求操作對(duì)所有線程都是可見(jiàn),只是這一點(diǎn)就會(huì)讓 Java 虛擬機(jī)的性能降低。JMM 就是在順序一致性內(nèi)存模型的基礎(chǔ)上,做了一些優(yōu)化:

  • 針對(duì)同步的多線程程序來(lái)說(shuō),也就是臨界區(qū)內(nèi)的代碼, JMM 允許發(fā)生重排序(但是不允許臨界區(qū)內(nèi)的代碼"逃逸"到臨界區(qū)之外,因?yàn)槿绻试S的話,就會(huì)破壞鎖的內(nèi)存語(yǔ)義)
  • 針對(duì)未同步的多線程程序來(lái)說(shuō), JMM 只提供最小安全性:線程讀取到的值,要么是之前某個(gè)線程寫(xiě)入的值,要么是默認(rèn)值,不會(huì)無(wú)中生有。

應(yīng)該能夠感覺(jué)到,相比于順序一致性內(nèi)存模型來(lái)說(shuō), JMM 給了編譯器和處理器一些空間,允許它們發(fā)生重排序。

這時(shí)候就有沖突點(diǎn)了:程序員這邊需要 JMM 提供一個(gè)強(qiáng)的內(nèi)存模型來(lái)編寫(xiě)代碼,也就是我代碼寫(xiě)的順序是什么樣,那程序執(zhí)行的時(shí)候就要是什么樣;但是編譯器和處理器則需要 JMM 對(duì)它們的約束越少越好,這樣它們就可以盡可能多的去做優(yōu)化,來(lái)提高性能

作為 JMM 這個(gè)中介者來(lái)說(shuō),既要滿足程序員的需求,又要滿足編譯器和處理器的需求,那就需要在這兩者之間找一個(gè)平衡點(diǎn),讓程序員寫(xiě)的代碼能夠產(chǎn)生他期望的結(jié)果,同時(shí)呢,也讓編譯器和處理器能夠做一些優(yōu)化

JMM 提出的解決方案就是:對(duì)于程序員,提供 happens-before 規(guī)則,這樣就滿足了程序員的需求 ---> 簡(jiǎn)單易懂,而且提供了足夠強(qiáng)的內(nèi)存可見(jiàn)性保證;對(duì)于編譯器和處理器來(lái)說(shuō),只要不改變程序的執(zhí)行結(jié)果(前提是正確同步了多線程程序),想怎么優(yōu)化就怎么優(yōu)化。

happens-before

終于講到了 happens-before 。

先來(lái)看 happens-before 關(guān)系的定義:

  • 如果一個(gè)操作 happens-before 另一個(gè)操作,那么第一個(gè)操作的執(zhí)行結(jié)果就會(huì)對(duì)第二個(gè)操作可見(jiàn)
  • 兩個(gè)操作之間如果存在 happens-before 關(guān)系,并不意味著 Java 平臺(tái)的具體實(shí)現(xiàn)就必須按照 happens-before 關(guān)系指定的順序來(lái)執(zhí)行。如果重排序之后的執(zhí)行結(jié)果,與按照 happens-before 關(guān)系來(lái)執(zhí)行的結(jié)果一直,那么 JMM 也允許這樣的重排序

看到這兒,你是不是覺(jué)得,這個(gè)怎么和 as-if-serial 語(yǔ)義一樣呢。沒(méi)錯(cuò), happens-before 關(guān)系本質(zhì)上和 as-if-serial 語(yǔ)義是一回事。

as-if-serial 語(yǔ)義保證的是單線程內(nèi)重排序之后的執(zhí)行結(jié)果和程序代碼本身應(yīng)該出現(xiàn)的結(jié)果是一致的, happens-before 關(guān)系保證的是正確同步的多線程程序的執(zhí)行結(jié)果不會(huì)被重排序改變。

一句話來(lái)總結(jié)就是:如果操作 A happens-before 操作 B ,那么操作 A 在內(nèi)存上所做的操作對(duì)操作 B 都是可見(jiàn)的,不管它們?cè)诓辉谝粋€(gè)線程。

在 Java 中,對(duì)于 happens-before 關(guān)系,有以下規(guī)定:

  • 程序順序規(guī)則:一個(gè)線程中的每一個(gè)操作, happens-before 于該線程中的任意后續(xù)操作
  • 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖, happens-before 于隨后對(duì)這個(gè)鎖的加鎖
  • volatile 變量規(guī)則:對(duì)一個(gè) volatile 域的寫(xiě), happens-before 與任意后續(xù)對(duì)這個(gè) volatile 域的讀
  • 傳遞性:如果 A happens-before B , 且 B happens-before C ,那么 A happens-before C
  • start 規(guī)則:如果線程 A 執(zhí)行操作 ThreadB。start() 啟動(dòng)線程 B ,那么 A 線程的 ThreadB。start() 操作 happens-before 于線程 B 中的任意操作
  • join 規(guī)則:如果線程 A 執(zhí)行操作 ThreadB。join() 并成功返回,那么線程 B 中的任意操作 happens-before 于線程 A 從 ThreadB。join() 操作成功返回。

寫(xiě)到這里,我感覺(jué)終于是寫(xiě)完這篇文章了,從為什么要重排序講到 happens-before 。

  • 參考:《Java 并發(fā)編程的藝術(shù)》

分享文章:為什么會(huì)有重排序?和 happens-before 有啥關(guān)系
標(biāo)題來(lái)源:http://www.5511xx.com/article/djpghjs.html