天天看點

【死磕Java并發】-----Java記憶體模型之重排序

在執行程式時,為了提供性能,處理器和編譯器常常會對指令進行重排序,但是不能随意重排序,不是你想怎麼排序就怎麼排序,它需要滿足以下兩個條件:

在單線程環境下不能改變程式運作的結果;

存在資料依賴關系的不允許重排序

如果看過LZ上篇部落格的就會知道,其實這兩點可以歸結于一點:無法通過happens-before原則推導出來的,JMM允許任意的排序。

as-if-serial語義的意思是,所有的操作均可以為了優化而被重排序,但是你必須要保證重排序後執行的結果不能被改變,編譯器、runtime、處理器都必須遵守as-if-serial語義。注意as-if-serial隻保證單線程環境,多線程環境下無效。

下面我們用一個簡單的示例來說明:

A、B、C三個操作存在如下關系:A、B不存在資料依賴關系,A和C、B和C存在資料依賴關系,是以在進行重排序的時候,A、B可以随意排序,但是必須位于C的前面,執行順序可以是A --> B --> C或者B --> A --> C。但是無論是何種執行順序最終的結果C總是等于3。

as-if-serail語義把單線程程式保護起來了,它可以保證在重排序的前提下程式的最終結果始終都是一緻的。

其實對于上段代碼,他們存在這樣的happen-before關系:

A happens-before B

B happens-before C

A happens-before C

1、2是程式順序次序規則,3是傳遞性。但是,不是說通過重排序,B可能會排在A之前執行麼,為何還會存在存在A happens-beforeB呢?這裡再次申明A happens-before B不是A一定會在B之前執行,而是A的對B可見,但是相對于這個程式A的執行結果不需要對B可見,且他們重排序後不會影響結果,是以JMM不會認為這種重排序非法。

我們需要明白這點:在不改變程式執行結果的前提下,盡可能提高程式的運作效率。

下面我們在看一段有意思的代碼:

按照重排序的規則,操作A與操作B有可能會進行重排序,如果重排序了,B會抛出異常( / by zero),此時A語句一定會執行不到,那麼a還會等于3麼?如果按照as-if-serial原則它就改變了程式的結果。其實JVM對異常做了一種特殊的處理,為了保證as-if-serial語義,Java異常處理機制對重排序做了一種特殊的處理:JIT在重排序時會在catch語句中插入錯誤代償代碼(a = 3),這樣做雖然會導緻cathc裡面的邏輯變得複雜,但是JIT優化原則是:盡可能地優化程式正常運作下的邏輯,哪怕以catch塊邏輯變得複雜為代價。

在單線程環境下由于as-if-serial語義,重排序無法影響最終的結果,但是對于多線程環境呢?

如下代碼(volatile的經典用法):

A線程執行writer(),線程B執行read(),線程B在執行時能否讀到 a = 1 呢?答案是不一定(注:X86CPU不支援寫寫重排序,如果是在x86上面操作,這個一定會是a=1,LZ搞了好久都沒有測試出來,最後查資料才發現)。

由于操作1 和操作2 之間沒有資料依賴性,是以可以進行重排序處理,操作3 和操作4 之間也沒有資料依賴性,他們亦可以進行重排序,但是操作3 和操作4 之間存在控制依賴性。假如操作1 和操作2 之間重排序:

【死磕Java并發】-----Java記憶體模型之重排序

按照這種執行順序線程B肯定讀不到線程A設定的a值,在這裡多線程的語義就已經被重排序破壞了。

操作3 和操作4 之間也可以重排序,這裡就不闡述了。但是他們之間存在一個控制依賴的關系,因為隻有操作3 成立操作4 才會執行。當代碼中存在控制依賴性時,會影響指令序列的執行的并行度,是以編譯器和處理器會采用猜測執行來克服控制依賴對并行度的影響。假如操作3 和操作4重排序了,操作4 先執行,則先會把計算結果臨時儲存到重排序緩沖中,當操作3 為真時才會将計算結果寫入變量i中

通過上面的分析,重排序不會影響單線程環境的執行結果,但是會破壞多線程的執行語義。

周志明 :《深入了解Java虛拟機》

方騰飛:《Java并發程式設計的藝術》

PS:如果你覺得文章對你有所幫助,别忘了推薦或者分享,因為有你的支援,才是我續寫下篇的動力和源泉!

作者:chenssy。一個專注于【死磕 Java】系列創作的男人

出處:https://www.cnblogs.com/chenssy/p/15738251.html

作者個人網站:https://www.cmsblogs.com/。專注于 Java 優質系列文章分享,提供一站式 Java 學習資料

目前死磕系列包括:

    1. 【死磕 Java 并發】:https://www.cmsblogs.com/category/1391296887813967872(已完成)

    2.【死磕 Spring 之 IOC】:https://www.cmsblogs.com/category/1391374860344758272(已完成)

    3.【死磕 Redis】:https://www.cmsblogs.com/category/1391389927996002304(已完成)

    4.【死磕 Java 基礎】:https://www.cmsblogs.com/category/1411518540095295488

    5.【死磕 NIO】:https://www.cmsblogs.com/article/1435620402348036096

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

繼續閱讀