重排序和happens-before有什么關(guān)系

本篇內(nèi)容介紹了“重排序和happens-before有什么關(guān)系”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

創(chuàng)新互聯(lián)是專業(yè)的耿馬網(wǎng)站建設(shè)公司,耿馬接單;提供成都網(wǎng)站設(shè)計、成都做網(wǎng)站,網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行耿馬網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊,希望更多企業(yè)前來合作!

舉個例子

在講重排序之前,先來看一個例子:

int a = 0, b = 0; public void methodOne(){     int one = a;     b = 1; } public void methodTwo(){     int two = b;     a = 2; }

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

那么我在這里有個問題, ( one , two ) 的值會是什么?

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

不錯,如果這個程序跑在了單線程上面,這樣回答一點兒毛病都沒有。

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

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

看到這兒,有沒有疑惑?為啥呢,怎么我寫的程序好好的,到 Java 虛擬機這里了,它就給我變了呢?

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

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

為什么會重排序

看完上面,你可能會有疑問,為什么會有重排序呢?

我的程序按照我自己的邏輯寫下來好好的沒啥問題, Java 虛擬機為什么動我的程序邏輯?

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

那么,重排序帶來了什么好處呢?

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

為了方便理解,我拿生活中的場景來舉例子。

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

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

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

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

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

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

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

重排序帶來的問題

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

因為重排序可以保證串行語義一致,但是沒有義務(wù)保證多線程間的語義也一致

凡是問題,都有辦法解決,要是沒有,那就再想想。

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

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

要說數(shù)據(jù)一致性的話,就要說一說,數(shù)據(jù)競爭。

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

  • 在一個線程中寫一個變量

  • 在另外一個線程中讀同一個變量

  • 寫和讀沒有通過同步來排序

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

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

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

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

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

  • 一個線程中的所有操作必須按照程序的順序來執(zhí)行(也就是按照寫的代碼的順序來執(zhí)行)

  • 不管程序是否同步,所有線程都只能看到一個單一的操作執(zhí)行順序。也就是說,在順序一致性模型中,每個操作必須是原子性的,而且立刻對所有線程都是可見的。

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

  • 針對同步的多線程程序來說,也就是臨界區(qū)內(nèi)的代碼, JMM  允許發(fā)生重排序(但是不允許臨界區(qū)內(nèi)的代碼"逃逸"到臨界區(qū)之外,因為如果允許的話,就會破壞鎖的內(nèi)存語義)

  • 針對未同步的多線程程序來說, JMM 只提供最小安全性:線程讀取到的值,要么是之前某個線程寫入的值,要么是默認(rèn)值,不會無中生有。

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

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

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

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

happens-before

終于講到了 happens-before 。

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

  • 如果一個操作 happens-before 另一個操作,那么第一個操作的執(zhí)行結(jié)果就會對第二個操作可見

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

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

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

一句話來總結(jié)就是:如果操作 A happens-before 操作 B ,那么操作 A 在內(nèi)存上所做的操作對操作 B  都是可見的,不管它們在不在一個線程。

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

  • 程序順序規(guī)則:一個線程中的每一個操作, happens-before 于該線程中的任意后續(xù)操作

  • 監(jiān)視器鎖規(guī)則:對一個鎖的解鎖, happens-before 于隨后對這個鎖的加鎖

  • volatile 變量規(guī)則:對一個 volatile 域的寫, happens-before 與任意后續(xù)對這個 volatile 域的讀

  • 傳遞性:如果 A happens-before B , 且 B happens-before C ,那么 A happens-before C

  • start 規(guī)則:如果線程 A 執(zhí)行操作 ThreadB。start() 啟動線程 B ,那么 A 線程的 ThreadB。start() 操作  happens-before 于線程 B 中的任意操作

  • join 規(guī)則:如果線程 A 執(zhí)行操作 ThreadB。join() 并成功返回,那么線程 B 中的任意操作 happens-before 于線程 A 從  ThreadB。join() 操作成功返回。

“重排序和happens-before有什么關(guān)系”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

當(dāng)前名稱:重排序和happens-before有什么關(guān)系
轉(zhuǎn)載源于:http://muchs.cn/article34/ghsjse.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、響應(yīng)式網(wǎng)站、移動網(wǎng)站建設(shè)網(wǎng)站策劃、網(wǎng)站改版、自適應(yīng)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站網(wǎng)頁設(shè)計