在寫線程池的時(shí)候,遇到了很多的問題,特別是happen-before應(yīng)該怎么去理解,怎么去利用,還有reorder,哪些操作有可能會被reorder?在這一點(diǎn)上,發(fā)現(xiàn)其實(shí)《concurrent in practice》也沒描述得太清晰。
在網(wǎng)上搜了一遍,發(fā)現(xiàn)JSR133的faq相對而言,還算稍微解釋了一下,發(fā)現(xiàn)JSR133其實(shí)也就40多頁,所以也就順帶看了一遍,因?yàn)榇蟛糠值膬?nèi)容都比較簡單(越往后看發(fā)現(xiàn)越復(fù)雜~),但是里面的定義比較難理解,所以只記錄了定義和一些個(gè)人認(rèn)為比較重要的地方,或者比較難理解的地方
這里是閱讀筆記,以備以后查閱
前言部分
1.下面的網(wǎng)站提供了附加的信息,幫助進(jìn)一步地理解JSR133
http://www.cs.umd.edu/~pugh/java/memoryModel/
2.在JLS中很可能需要JVM(TM)實(shí)現(xiàn)的兩個(gè)原始的定義的改變:
一.introduction
3.JSR133并不是描述多線程程序應(yīng)該怎么去執(zhí)行,而是描述多線程程序允許怎么去顯示的,他包括一些規(guī)則,這些規(guī)則定義了一個(gè)被多線程更新的共享變量的值,是否是可見的。(比如讀一個(gè)共享變量的值,根據(jù)規(guī)則,應(yīng)該顯示的是什么)
4.還是synchronized的問題,該問題在之前的文章中也提到過,在方法上的時(shí)候,它鎖的是this,如果是靜態(tài)方法,那么鎖的是方法所在類的class
二.Incorrenctly Synchronized Programs Exhibit Surprising Behaviors
5.不合適的同步(improperly synchronized):(注:并不意味著錯誤)
三.Informal Semantics
6.正確的同步(correct synchronization)(嚴(yán)格地保證多線程訪問的正確性,但吞吐率很低的)
理解一個(gè)程序是否被正確地同步,有兩個(gè)關(guān)鍵點(diǎn):
這里有一個(gè)很有意思的圖片,可能對別人沒太多的意義,但對我自己而言,真是解釋了不少以前的迷惑,最主要是第二幅圖片的,之前有一個(gè)誤解:數(shù)據(jù)只有在同步塊中,而另一個(gè)線程必須也進(jìn)入一個(gè)相同的鎖的同步塊中,才能保證其可見性。但其實(shí)不是這樣的,只要能夠保證順序一致性,就是可見的,如圖a
7.final field
在這一節(jié),并沒有解釋final field是怎么保證其同步的(根據(jù)之前的理解,如果有final field,應(yīng)該在對象的構(gòu)造完成之后做了一些事來保證同步,但到底做了什么事?還得看下文第9章),只是定義了final field在構(gòu)造完成后,就不會再改變,所以只需要在構(gòu)造器中,保證其沒有escape,就可以正確地在并發(fā)環(huán)境中無限制地使用了。
四.What is a Memory Model?
8.存儲模型(memory model)的定義:
一個(gè)存儲模型(memory model)描述的是,給定一個(gè)程序和這個(gè)程序的執(zhí)行軌跡(trace),就可以判斷這個(gè)執(zhí)行軌跡(trace)是否是合法的。在Java程序語言中,存儲模型(memory model)是這樣工作的:檢查在運(yùn)行軌跡(trace)中的每個(gè)讀,并且根據(jù)一定的規(guī)則,校驗(yàn)這個(gè)讀觀察到的寫是否是有效的。
存儲模型(memory model)描述一個(gè)程序可能的舉止。一個(gè)存儲模型(memory model)實(shí)現(xiàn)可以隨意地產(chǎn)生任何的代碼(像reorder,刪除一些沒必要的同步),只要所有的程序運(yùn)行結(jié)果可以根據(jù)存儲模型(memory model)來預(yù)測。
9.JVM做的一些事
當(dāng)我們說讀(read),我們只是說的是一些這樣的動作:如讀一些fields或者數(shù)組。其他操作的語義,像讀一個(gè)數(shù)組的長度,執(zhí)行一個(gè)類型轉(zhuǎn)換(checked casts),或者調(diào)用一個(gè)虛擬的方法(invocations of virtual methodds,個(gè)人的理解應(yīng)該是調(diào)用接口的一個(gè)方法),是不會直接受數(shù)據(jù)競爭的影響的。JVM的實(shí)現(xiàn)保證了數(shù)據(jù)競爭不會導(dǎo)致錯誤的舉動,像返回一個(gè)錯誤的數(shù)組長度,或者調(diào)用虛擬方法的錯誤(在競爭的數(shù)據(jù)中,應(yīng)該是可能發(fā)生的)。
五.Definitions
10.共享變量/堆內(nèi)存(Shared variables/Heap memory):
能夠在線程間被共享的內(nèi)存被叫做共享內(nèi)存或者堆內(nèi)存。所有的實(shí)例域(instance fields),靜態(tài)域(static fileds)和數(shù)組都被存在堆內(nèi)存。我們用變量(variable)來引用所有的域和數(shù)組。在一個(gè)方法中的本地變量不會在線程中共享,也不會受存儲模型(memory model)影響。
11.線程交互動作(Inter-thread Actions):
一個(gè)線程交互動作(inter-thread action)是這樣的動作:它在一個(gè)線程中被調(diào)用,可以被其他線程檢測或者直接影響。inter-thread actions包括讀和寫共享變量和同步動作,像lock或者unlock,讀或者寫一個(gè)volatile變量,和開始一個(gè)線程。同時(shí),也包括一些與外部世界交互的動作(external actions),和可以導(dǎo)致一個(gè)線程進(jìn)入無限循環(huán)的動作(thread divergence actions)。
每一個(gè)線程交互動作(inter-thread action)都是與這個(gè)動作相關(guān)的信息聯(lián)系在一起的。所有的動作都與它被調(diào)用的線程聯(lián)系在一起,并且和線程中的程序順序(Program order)聯(lián)系在一起。附加的聯(lián)系信息包括:
write The variable written to and the value written.
read The variable read and the write seen (from this, we can determine
the value seen).
lock The monitor which is locked.
unlock The monitor which is unlocked.
12.程序順序(Program order):
根據(jù)線程內(nèi)語義(intra-thread semantics),包括所有的線程t中的線程交互動作(inter-hread actions),和所有的動作在內(nèi),t的程序順序(program order)是唯一影響執(zhí)行順序的。(這個(gè)定義應(yīng)該是說在線程中,表現(xiàn)出來的執(zhí)行順序永遠(yuǎn)是一致的,即使某些動作與其他線程有交互)
13.線程內(nèi)語義(Intra-thread semantics):
線程內(nèi)語義(Intra-thread semantics)是一個(gè)單線程程序的基本語義,它允許根據(jù)在線程中讀操作看到的值對線程的動作的完整的預(yù)測。為了確定線程中的動作是否是合法的,我們只是簡單地評估在單線程中它的正確性,像定義在JLS中的。
每次線程t的評估產(chǎn)生一個(gè)線程交互動作(inter-thread action),它必須匹配這個(gè)程序順序中的下一個(gè)動作a。如果a是一個(gè)讀操作,那么進(jìn)一步的評估會根據(jù)存儲模型(memory model)并使用看到的a的值做決定。
簡單地說:線程內(nèi)語義(intra-thread semantics)決定了在一個(gè)單獨(dú)的線程中的執(zhí)行。當(dāng)值是從堆里面讀出來的,他們就由存儲模型(memory model)來決定
14.同步動作(Synchronization Actions):
同步動作包括lock,unlock,對volatile變量的讀和寫,開始一個(gè)線程的動作start,檢測一個(gè)線程是否結(jié)束的動作join等。任何在一個(gè)synchronizes-with邊緣(edge),包括開始和結(jié)束點(diǎn),都是一個(gè)同步動作(synchronization action)。這樣的動作會在后面的happens-before邊緣(edge)更詳細(xì)地列出來
15.同步順序(Synchronization order):
每個(gè)執(zhí)行有一個(gè)同步順序(怎么理解?)。一個(gè)同步順序是包括所有的在該執(zhí)行中的同步動作的所有順序。(也不是太理解)
16.Happens-Before and Synchronizes-With Edges:
同步動作(synchronized action)也會引發(fā)happen-before邊緣,我們將結(jié)果指向邊緣(directed edges)叫做synchroized-with edges。他們定義在下面:
17.happen-before規(guī)則的稍微詳細(xì)的解釋:
注意這一點(diǎn):兩個(gè)動作間happens-before關(guān)系的存在,并不意味著他們在實(shí)現(xiàn)上也按這個(gè)順序來執(zhí)行。如果一個(gè)重排序產(chǎn)生的結(jié)果與合法的操作一致,它就不是非法的。舉個(gè)例子:對一個(gè)對象中所有變量(field)默認(rèn)值的寫操作并不一定要發(fā)生在線程開始后的動作之前,只要沒有任何的讀操作觀察到這個(gè)事實(shí)。(也就是說,如果沒有操作去讀,即使有happen-before關(guān)系,也是允許重排序的,這其實(shí)也解釋了為什么我們在其他線程中能觀察到該線程的亂序操作,因?yàn)樵诒揪€程中雖然有重排序操作,但是沒有讀操作,所以允許它重排序)
進(jìn)一步說,如果兩個(gè)操作享有一個(gè)happens-before的關(guān)系,另一個(gè)沒有享有該happen-before關(guān)系的動作,不一定能觀察到他們這個(gè)happen-before的順序,有可能仍然是亂序的。
六.Approximations to a Java Memory Model
這一章看了幾遍,感覺還是有點(diǎn)模糊,大概的意思算是有點(diǎn)理解了(如果有偏差,還請指出)
18.Happens-Before Memory Model
在定義JMM之前,先定義一個(gè)能滿足JMM的所有要求,但還存在因果循環(huán)問題(causal loops,即因?yàn)锳得出B,因?yàn)锽得出C,因?yàn)镃得出D,因?yàn)镈得出A,因?yàn)檫@整條鏈沒有一個(gè)起因,而實(shí)際上又會發(fā)生,這就叫因果循環(huán)問題)的一個(gè)模型。
對這個(gè)模型的描述在這里就不翻譯了,很簡單,也很模糊,翻譯肯定翻譯不清楚的。
這里就列舉一下該模型存在的問題的例子:(這些問題都是由對模型的定義推出的合法的而且是正確的問題,但可以看到存在明顯的因果循環(huán),我們是不能接受的)
在Happens-Before Memory Model中,會出現(xiàn)這樣的結(jié)果x=y=1
在Happens-Before Memory Model中,JVM允許這樣的優(yōu)化,所以也會導(dǎo)致因果循環(huán)的問題
很明顯,我們應(yīng)該允許寫操作的提前提交,以得到效能優(yōu)化的效果(如上圖的右圖),但有些操作應(yīng)該不允許的。不正式地說:當(dāng)涉及到數(shù)據(jù)競爭的時(shí)候,就不應(yīng)該允許提前提交,否則,就是允許的。
七.Formal Specification of the Java Memory Model
這里就只總結(jié)一下非常概括性的內(nèi)容吧(因?yàn)檫@些定義都沒有例子,難以理解,先看了下文再回來詳細(xì)理解這里的定義)
19.JMM的正式的定義
定義動作和執(zhí)行:
用一個(gè)元組定義動作a的概念
用另一個(gè)元組定義一個(gè)執(zhí)行的概念
然后對一些動作進(jìn)行了定義:
外部動作(external actions):略
線程分歧動作(thread divergence action):略
synchronizes-with:略
happens-before略
足夠同步邊緣(sufficient synchronization edges)略
偏序和功能的限制(Restrictions of partial orders and functions)略
設(shè)計(jì)良好的執(zhí)行(Well-Formed Executions):
對執(zhí)行的因果關(guān)系的需求(即解決上文所說的因果循環(huán)問題的需求):
這是一個(gè)推導(dǎo)過程,需要滿足9個(gè)條件,就可以推出這個(gè)結(jié)論,全是數(shù)學(xué)公式過程,在這里就不抄了(下文的例子貌似就是用這些條件作為依據(jù)推理的,看了下文后,再看這里應(yīng)該能清晰點(diǎn))
能被觀察到的舉動:略
永不終止的執(zhí)行:略
八.Illustrative Test Cases and Behaviors
20.以下圖片中出現(xiàn)的情況都是合法的
至于為什么合法,有一定的解釋:因?yàn)樗梢詽M足上文中的“對執(zhí)行的因果關(guān)系的需求”中的9個(gè)條件
很明顯,將y=1提前了,是允許的(文章中有根據(jù)條件對提交步驟的推理,證明是可行的,之后的例子都沒有,如果時(shí)間空閑,后面將試著補(bǔ)充推導(dǎo)過程)
也是一個(gè)指令的重排序,但注意:r1==1或者r2==2是不允許的:如果寫提前了,他們對本地的讀就會看不到這個(gè)寫。
編譯器會優(yōu)化這個(gè)==操作,變成if(true),所以將b=2提前了,也是允許的
編譯器會發(fā)現(xiàn)分支無論怎么走,都會執(zhí)行a=1,所以也提前了
編譯器會發(fā)現(xiàn)r1要么為1要么為0,所以r2必然=1,所以也提前了
這個(gè)現(xiàn)象感覺實(shí)在沒辦法解釋了,文中的解釋感覺也不通~~
翻譯如下:
編譯器會發(fā)現(xiàn),唯一有可能分配到x的值是0或者42。根據(jù)這個(gè),編譯器可以推斷:當(dāng)執(zhí)行到r1=x,要么剛好執(zhí)行了一個(gè)寫x=42,要么剛好讀x,并且看到了值42(為什么?根本解釋不通的~)。無論是哪種情況,一個(gè)對x的讀看到了值42,然后它會將r1=x改為r1=42,這也會允許y=r1轉(zhuǎn)變?yōu)閥=42,并且提早發(fā)生了,這樣就出現(xiàn)了描述的情況:r1=r2=r3=42
21.以下圖片中的情況是不合法的
但解釋挺牽強(qiáng)的,說是由于安全的原因,比如在第二個(gè)圖中,如果42是對某個(gè)對象的引用,而這個(gè)引用是Thread4持有的,他打算只有當(dāng)z=1時(shí),才讓Thread1和Thread2看到。
如果發(fā)生了tu2的情況,安全就沒有保證了,所以不允許~~
九.Final Field Semantics
22.final filed的需求和目標(biāo)final field的值將不會改變
只包含final filed的對象在“成功構(gòu)造”(注意,這是關(guān)鍵的必要條件,也就是在構(gòu)造成功之后,this才能被別的線程看到)結(jié)束后,在可能有競爭的線程間傳遞,需要被認(rèn)為是不變的。
對final field的讀,將最小化編譯器/結(jié)構(gòu)的消耗(cost)(相對于非final field)
final field的語義需要允許某些場景(如反序列化),在這些場景中,對象中的final field允許構(gòu)造結(jié)束后被修改(順便瀏覽了一下反序列化的源碼(見以下代碼),還真是先創(chuàng)建了對象,然后才讀入field的值的,以后要是面試面到了final field,又可以忽悠一下)
23.final field safe context
為了防止final field在構(gòu)造器中的設(shè)值與發(fā)生在之后的讀的重排序(reorder),定義了一個(gè)叫final field safe context的區(qū)域。
文章是這樣解釋的:如果一個(gè)對象在final field safe context中被構(gòu)造(注意,這是必要條件,如果是在final field safe context中,但對象沒有被構(gòu)造,也就沒有該限制),對該對象的final field的值的讀,將不會與發(fā)生在final field safe context中的寫進(jìn)行重排序。
舉個(gè)例子:像在clone方法和ObjectInputStream.readObject方法中使用這樣一個(gè)final field safe context就不會出現(xiàn)上述重排序的問題了(這些例子都是構(gòu)造成功后修改final field的值的情況)。(但這個(gè)final field safe context怎么加?如果我們要人為地利用這個(gè)東西,怎么去編碼呢?文章沒有提及,而瀏覽了一下clone和readObject方法,也沒有調(diào)用相應(yīng)的本地方法,那這個(gè)final field safe context是如何被調(diào)用的呢?方法覆蓋?)
24.final field的正式語義
因?yàn)楸容^重要,這也貼一下原文,以防被小弟誤譯了
A freeze action on a final field f of an object o takes place when a constructor for o in which f is written exits,either abruptly or normally
如果一個(gè)對象o的包含對final field的寫操作的構(gòu)造方法發(fā)生,一個(gè)對final field的一個(gè)固化操作將會被調(diào)用,即使這個(gè)構(gòu)造方法可以是突然的,或者正常的。
進(jìn)一步的解釋:
一些特殊的機(jī)制像反射,反序列化,是允許在構(gòu)造結(jié)束后修改final field的值的。像可以使用java.lang.reflect中的setX(...)方法來達(dá)到這個(gè)效果。當(dāng)這個(gè)fiedl是final的,這個(gè)方法只有滿足2個(gè)條件才能設(shè)置成功,否則會拋IllegalAccessException異常:這個(gè)field不是靜態(tài)的,并且對這個(gè)field設(shè)置setAccessible(true)成功。
已有 0 人發(fā)表留言,猛擊->>這里<<-參與討論
ItEye推薦
在網(wǎng)上搜了一遍,發(fā)現(xiàn)JSR133的faq相對而言,還算稍微解釋了一下,發(fā)現(xiàn)JSR133其實(shí)也就40多頁,所以也就順帶看了一遍,因?yàn)榇蟛糠值膬?nèi)容都比較簡單(越往后看發(fā)現(xiàn)越復(fù)雜~),但是里面的定義比較難理解,所以只記錄了定義和一些個(gè)人認(rèn)為比較重要的地方,或者比較難理解的地方
這里是閱讀筆記,以備以后查閱
前言部分
1.下面的網(wǎng)站提供了附加的信息,幫助進(jìn)一步地理解JSR133
http://www.cs.umd.edu/~pugh/java/memoryModel/
2.在JLS中很可能需要JVM(TM)實(shí)現(xiàn)的兩個(gè)原始的定義的改變:
- volatile變量的語義被加強(qiáng)了,以前的語義是允許自由地被reorder的
- final的語義也被加強(qiáng)了,現(xiàn)在可以不需要顯性得同步,就可以獲得線程安全的不變性。這可能需要在含有設(shè)置final field的構(gòu)造函數(shù)的結(jié)尾,加入一些存儲屏障的步驟
一.introduction
3.JSR133并不是描述多線程程序應(yīng)該怎么去執(zhí)行,而是描述多線程程序允許怎么去顯示的,他包括一些規(guī)則,這些規(guī)則定義了一個(gè)被多線程更新的共享變量的值,是否是可見的。(比如讀一個(gè)共享變量的值,根據(jù)規(guī)則,應(yīng)該顯示的是什么)
4.還是synchronized的問題,該問題在之前的文章中也提到過,在方法上的時(shí)候,它鎖的是this,如果是靜態(tài)方法,那么鎖的是方法所在類的class
二.Incorrenctly Synchronized Programs Exhibit Surprising Behaviors
5.不合適的同步(improperly synchronized):(注:并不意味著錯誤)
- 一個(gè)線程寫
- 另一個(gè)線程讀
- 讀和寫沒有用synchronized來保證順序
三.Informal Semantics
6.正確的同步(correct synchronization)(嚴(yán)格地保證多線程訪問的正確性,但吞吐率很低的)
理解一個(gè)程序是否被正確地同步,有兩個(gè)關(guān)鍵點(diǎn):
- 沖突的訪問(Conflicting Accesses):對同一個(gè)共享域或共享數(shù)組的多個(gè)訪問,并且這些訪問至少有一個(gè)是寫,就說明有沖突。
- Happens-Before關(guān)系 :如果一個(gè)動作happens-before另一個(gè),前者對后者是可見的,并且在執(zhí)行順序上也會在后者的前面。 這點(diǎn)必須強(qiáng)調(diào)一下:一個(gè)happens-before關(guān)系,并不是暗示這些動作在java平臺實(shí)現(xiàn)中,必須按這樣的順序去執(zhí)行。(這里并不是很理解,難道也會是一個(gè)幻象?)原話是這樣的:It should be stressed that a happens-before relationship between two actions does not imply that those actions must occur in that order in a Java platform implementation.
Happens-before關(guān)系最主要是強(qiáng)調(diào)并定義了兩個(gè)有競爭的動作之間的順序(當(dāng)數(shù)據(jù)競爭出現(xiàn)時(shí))
happens-before的規(guī)則包括:(注意,這些動作在直覺上是本該如此的,但在多線程中,就不一定了,所以才有這些規(guī)則)
a.在這個(gè)線程中,每個(gè)動作happens-before每個(gè)之后的動作。
b.一個(gè)對固有鎖(monitor)的unlock操作happens-before每個(gè)之后的對monitor的lock操作。
c.一個(gè)對volatile filed的寫happens-before每個(gè)之后的對該volatile的讀(注意,這里沒有線程的限制)
d.一個(gè)對線程的start()的調(diào)用(call)happens-before該線程中任何動作。
e.在線程中的所有動作happens-before任何其他線程成功地調(diào)用對該線程的join()返回
f.如果a happens-before b,b happens-before c,那么a happens-before c,即具有傳遞性
這里有一個(gè)很有意思的圖片,可能對別人沒太多的意義,但對我自己而言,真是解釋了不少以前的迷惑,最主要是第二幅圖片的,之前有一個(gè)誤解:數(shù)據(jù)只有在同步塊中,而另一個(gè)線程必須也進(jìn)入一個(gè)相同的鎖的同步塊中,才能保證其可見性。但其實(shí)不是這樣的,只要能夠保證順序一致性,就是可見的,如圖a

7.final field
在這一節(jié),并沒有解釋final field是怎么保證其同步的(根據(jù)之前的理解,如果有final field,應(yīng)該在對象的構(gòu)造完成之后做了一些事來保證同步,但到底做了什么事?還得看下文第9章),只是定義了final field在構(gòu)造完成后,就不會再改變,所以只需要在構(gòu)造器中,保證其沒有escape,就可以正確地在并發(fā)環(huán)境中無限制地使用了。
四.What is a Memory Model?
8.存儲模型(memory model)的定義:
一個(gè)存儲模型(memory model)描述的是,給定一個(gè)程序和這個(gè)程序的執(zhí)行軌跡(trace),就可以判斷這個(gè)執(zhí)行軌跡(trace)是否是合法的。在Java程序語言中,存儲模型(memory model)是這樣工作的:檢查在運(yùn)行軌跡(trace)中的每個(gè)讀,并且根據(jù)一定的規(guī)則,校驗(yàn)這個(gè)讀觀察到的寫是否是有效的。
存儲模型(memory model)描述一個(gè)程序可能的舉止。一個(gè)存儲模型(memory model)實(shí)現(xiàn)可以隨意地產(chǎn)生任何的代碼(像reorder,刪除一些沒必要的同步),只要所有的程序運(yùn)行結(jié)果可以根據(jù)存儲模型(memory model)來預(yù)測。
9.JVM做的一些事
當(dāng)我們說讀(read),我們只是說的是一些這樣的動作:如讀一些fields或者數(shù)組。其他操作的語義,像讀一個(gè)數(shù)組的長度,執(zhí)行一個(gè)類型轉(zhuǎn)換(checked casts),或者調(diào)用一個(gè)虛擬的方法(invocations of virtual methodds,個(gè)人的理解應(yīng)該是調(diào)用接口的一個(gè)方法),是不會直接受數(shù)據(jù)競爭的影響的。JVM的實(shí)現(xiàn)保證了數(shù)據(jù)競爭不會導(dǎo)致錯誤的舉動,像返回一個(gè)錯誤的數(shù)組長度,或者調(diào)用虛擬方法的錯誤(在競爭的數(shù)據(jù)中,應(yīng)該是可能發(fā)生的)。
五.Definitions
10.共享變量/堆內(nèi)存(Shared variables/Heap memory):
能夠在線程間被共享的內(nèi)存被叫做共享內(nèi)存或者堆內(nèi)存。所有的實(shí)例域(instance fields),靜態(tài)域(static fileds)和數(shù)組都被存在堆內(nèi)存。我們用變量(variable)來引用所有的域和數(shù)組。在一個(gè)方法中的本地變量不會在線程中共享,也不會受存儲模型(memory model)影響。
11.線程交互動作(Inter-thread Actions):
一個(gè)線程交互動作(inter-thread action)是這樣的動作:它在一個(gè)線程中被調(diào)用,可以被其他線程檢測或者直接影響。inter-thread actions包括讀和寫共享變量和同步動作,像lock或者unlock,讀或者寫一個(gè)volatile變量,和開始一個(gè)線程。同時(shí),也包括一些與外部世界交互的動作(external actions),和可以導(dǎo)致一個(gè)線程進(jìn)入無限循環(huán)的動作(thread divergence actions)。
每一個(gè)線程交互動作(inter-thread action)都是與這個(gè)動作相關(guān)的信息聯(lián)系在一起的。所有的動作都與它被調(diào)用的線程聯(lián)系在一起,并且和線程中的程序順序(Program order)聯(lián)系在一起。附加的聯(lián)系信息包括:
write The variable written to and the value written.
read The variable read and the write seen (from this, we can determine
the value seen).
lock The monitor which is locked.
unlock The monitor which is unlocked.
12.程序順序(Program order):
根據(jù)線程內(nèi)語義(intra-thread semantics),包括所有的線程t中的線程交互動作(inter-hread actions),和所有的動作在內(nèi),t的程序順序(program order)是唯一影響執(zhí)行順序的。(這個(gè)定義應(yīng)該是說在線程中,表現(xiàn)出來的執(zhí)行順序永遠(yuǎn)是一致的,即使某些動作與其他線程有交互)
13.線程內(nèi)語義(Intra-thread semantics):
線程內(nèi)語義(Intra-thread semantics)是一個(gè)單線程程序的基本語義,它允許根據(jù)在線程中讀操作看到的值對線程的動作的完整的預(yù)測。為了確定線程中的動作是否是合法的,我們只是簡單地評估在單線程中它的正確性,像定義在JLS中的。
每次線程t的評估產(chǎn)生一個(gè)線程交互動作(inter-thread action),它必須匹配這個(gè)程序順序中的下一個(gè)動作a。如果a是一個(gè)讀操作,那么進(jìn)一步的評估會根據(jù)存儲模型(memory model)并使用看到的a的值做決定。
簡單地說:線程內(nèi)語義(intra-thread semantics)決定了在一個(gè)單獨(dú)的線程中的執(zhí)行。當(dāng)值是從堆里面讀出來的,他們就由存儲模型(memory model)來決定
14.同步動作(Synchronization Actions):
同步動作包括lock,unlock,對volatile變量的讀和寫,開始一個(gè)線程的動作start,檢測一個(gè)線程是否結(jié)束的動作join等。任何在一個(gè)synchronizes-with邊緣(edge),包括開始和結(jié)束點(diǎn),都是一個(gè)同步動作(synchronization action)。這樣的動作會在后面的happens-before邊緣(edge)更詳細(xì)地列出來
15.同步順序(Synchronization order):
每個(gè)執(zhí)行有一個(gè)同步順序(怎么理解?)。一個(gè)同步順序是包括所有的在該執(zhí)行中的同步動作的所有順序。(也不是太理解)
16.Happens-Before and Synchronizes-With Edges:
同步動作(synchronized action)也會引發(fā)happen-before邊緣,我們將結(jié)果指向邊緣(directed edges)叫做synchroized-with edges。他們定義在下面:
- 一個(gè)unlock動作synchronizes-with所有之后的在同一個(gè)鎖上的lock動作(這里“之后的”的含義根據(jù)synchronization order定義)
- 一個(gè)對volatile變量的寫synchronizes-with所有之后的對該變量的讀操作(任何線程)(這里“之后的”的含義根據(jù)synchronization order定義)
- 一個(gè)開始一個(gè)線程的動作synchronizes-with線程開始后的第一個(gè)操作
- 線程T1的最后一個(gè)動作synchronizes-with另一個(gè)線程T2檢測到T1已經(jīng)停止后的任何動作。T2可以通過調(diào)用T1.isAlive()或者join動作來完成這個(gè)操作。
- 如果線程T1中斷(interrupts)線程T2,T1的interrupt動作synchronizes-with任何其他的線程(包括T2)檢測到T2被中斷interrupted。
- 對每個(gè)變量的默認(rèn)值的寫synchronizes-with每個(gè)線程的第一個(gè)動作。
- 對一個(gè)對象的finalizer的調(diào)用,有一個(gè)隱性的對該對象引用的讀操作。在對象的構(gòu)造器的結(jié)尾和這個(gè)讀操作之間,有一個(gè)happens-before edge。 注意:所有的對這個(gè)對象的凍結(jié)(freezes)happen-before這個(gè)happens-before edge的開始點(diǎn)。(這段比較難理解,在9.2會重新提及,暫時(shí)先記錄下來)
17.happen-before規(guī)則的稍微詳細(xì)的解釋:
注意這一點(diǎn):兩個(gè)動作間happens-before關(guān)系的存在,并不意味著他們在實(shí)現(xiàn)上也按這個(gè)順序來執(zhí)行。如果一個(gè)重排序產(chǎn)生的結(jié)果與合法的操作一致,它就不是非法的。舉個(gè)例子:對一個(gè)對象中所有變量(field)默認(rèn)值的寫操作并不一定要發(fā)生在線程開始后的動作之前,只要沒有任何的讀操作觀察到這個(gè)事實(shí)。(也就是說,如果沒有操作去讀,即使有happen-before關(guān)系,也是允許重排序的,這其實(shí)也解釋了為什么我們在其他線程中能觀察到該線程的亂序操作,因?yàn)樵诒揪€程中雖然有重排序操作,但是沒有讀操作,所以允許它重排序)
進(jìn)一步說,如果兩個(gè)操作享有一個(gè)happens-before的關(guān)系,另一個(gè)沒有享有該happen-before關(guān)系的動作,不一定能觀察到他們這個(gè)happen-before的順序,有可能仍然是亂序的。
六.Approximations to a Java Memory Model
這一章看了幾遍,感覺還是有點(diǎn)模糊,大概的意思算是有點(diǎn)理解了(如果有偏差,還請指出)
18.Happens-Before Memory Model
在定義JMM之前,先定義一個(gè)能滿足JMM的所有要求,但還存在因果循環(huán)問題(causal loops,即因?yàn)锳得出B,因?yàn)锽得出C,因?yàn)镃得出D,因?yàn)镈得出A,因?yàn)檫@整條鏈沒有一個(gè)起因,而實(shí)際上又會發(fā)生,這就叫因果循環(huán)問題)的一個(gè)模型。
對這個(gè)模型的描述在這里就不翻譯了,很簡單,也很模糊,翻譯肯定翻譯不清楚的。
這里就列舉一下該模型存在的問題的例子:(這些問題都是由對模型的定義推出的合法的而且是正確的問題,但可以看到存在明顯的因果循環(huán),我們是不能接受的)

在Happens-Before Memory Model中,會出現(xiàn)這樣的結(jié)果x=y=1

在Happens-Before Memory Model中,JVM允許這樣的優(yōu)化,所以也會導(dǎo)致因果循環(huán)的問題
很明顯,我們應(yīng)該允許寫操作的提前提交,以得到效能優(yōu)化的效果(如上圖的右圖),但有些操作應(yīng)該不允許的。不正式地說:當(dāng)涉及到數(shù)據(jù)競爭的時(shí)候,就不應(yīng)該允許提前提交,否則,就是允許的。
七.Formal Specification of the Java Memory Model
這里就只總結(jié)一下非常概括性的內(nèi)容吧(因?yàn)檫@些定義都沒有例子,難以理解,先看了下文再回來詳細(xì)理解這里的定義)
19.JMM的正式的定義
定義動作和執(zhí)行:
用一個(gè)元組定義動作a的概念

用另一個(gè)元組定義一個(gè)執(zhí)行的概念

然后對一些動作進(jìn)行了定義:
外部動作(external actions):略
線程分歧動作(thread divergence action):略
synchronizes-with:略
happens-before略
足夠同步邊緣(sufficient synchronization edges)略
偏序和功能的限制(Restrictions of partial orders and functions)略
設(shè)計(jì)良好的執(zhí)行(Well-Formed Executions):
- 每個(gè)對變量x的讀操作看到一個(gè)對x變量的寫操作(注意,不是說前一個(gè)),所有對volatile變量的讀和寫都是volatile操作(沒理解好~)
- 同步順序(Synchronization order)與程序順序(program order)和互斥(mutual exclusion)保持一致
- 執(zhí)行遵守線程內(nèi)(intra-thread語義,即在線程內(nèi)與程序順序一致)一致性
- 執(zhí)行遵守同步順序(synchronization-order,這個(gè)也沒理解好)一致性
- 執(zhí)行遵守同步順序(happens-before)一致性
對執(zhí)行的因果關(guān)系的需求(即解決上文所說的因果循環(huán)問題的需求):
這是一個(gè)推導(dǎo)過程,需要滿足9個(gè)條件,就可以推出這個(gè)結(jié)論,全是數(shù)學(xué)公式過程,在這里就不抄了(下文的例子貌似就是用這些條件作為依據(jù)推理的,看了下文后,再看這里應(yīng)該能清晰點(diǎn))
能被觀察到的舉動:略
永不終止的執(zhí)行:略
八.Illustrative Test Cases and Behaviors
20.以下圖片中出現(xiàn)的情況都是合法的
至于為什么合法,有一定的解釋:因?yàn)樗梢詽M足上文中的“對執(zhí)行的因果關(guān)系的需求”中的9個(gè)條件

很明顯,將y=1提前了,是允許的(文章中有根據(jù)條件對提交步驟的推理,證明是可行的,之后的例子都沒有,如果時(shí)間空閑,后面將試著補(bǔ)充推導(dǎo)過程)

也是一個(gè)指令的重排序,但注意:r1==1或者r2==2是不允許的:如果寫提前了,他們對本地的讀就會看不到這個(gè)寫。

編譯器會優(yōu)化這個(gè)==操作,變成if(true),所以將b=2提前了,也是允許的

編譯器會發(fā)現(xiàn)分支無論怎么走,都會執(zhí)行a=1,所以也提前了

編譯器會發(fā)現(xiàn)r1要么為1要么為0,所以r2必然=1,所以也提前了

這個(gè)現(xiàn)象感覺實(shí)在沒辦法解釋了,文中的解釋感覺也不通~~
翻譯如下:
編譯器會發(fā)現(xiàn),唯一有可能分配到x的值是0或者42。根據(jù)這個(gè),編譯器可以推斷:當(dāng)執(zhí)行到r1=x,要么剛好執(zhí)行了一個(gè)寫x=42,要么剛好讀x,并且看到了值42(為什么?根本解釋不通的~)。無論是哪種情況,一個(gè)對x的讀看到了值42,然后它會將r1=x改為r1=42,這也會允許y=r1轉(zhuǎn)變?yōu)閥=42,并且提早發(fā)生了,這樣就出現(xiàn)了描述的情況:r1=r2=r3=42
21.以下圖片中的情況是不合法的


但解釋挺牽強(qiáng)的,說是由于安全的原因,比如在第二個(gè)圖中,如果42是對某個(gè)對象的引用,而這個(gè)引用是Thread4持有的,他打算只有當(dāng)z=1時(shí),才讓Thread1和Thread2看到。
如果發(fā)生了tu2的情況,安全就沒有保證了,所以不允許~~
九.Final Field Semantics
22.final filed的需求和目標(biāo)
Object curObj = curContext.getObj(); ObjectStreamClass curDesc = curContext.getDesc(); bin.setBlockDataMode(false); defaultReadFields(curObj, curDesc);
23.final field safe context
為了防止final field在構(gòu)造器中的設(shè)值與發(fā)生在之后的讀的重排序(reorder),定義了一個(gè)叫final field safe context的區(qū)域。
文章是這樣解釋的:如果一個(gè)對象在final field safe context中被構(gòu)造(注意,這是必要條件,如果是在final field safe context中,但對象沒有被構(gòu)造,也就沒有該限制),對該對象的final field的值的讀,將不會與發(fā)生在final field safe context中的寫進(jìn)行重排序。
舉個(gè)例子:像在clone方法和ObjectInputStream.readObject方法中使用這樣一個(gè)final field safe context就不會出現(xiàn)上述重排序的問題了(這些例子都是構(gòu)造成功后修改final field的值的情況)。(但這個(gè)final field safe context怎么加?如果我們要人為地利用這個(gè)東西,怎么去編碼呢?文章沒有提及,而瀏覽了一下clone和readObject方法,也沒有調(diào)用相應(yīng)的本地方法,那這個(gè)final field safe context是如何被調(diào)用的呢?方法覆蓋?)
24.final field的正式語義
因?yàn)楸容^重要,這也貼一下原文,以防被小弟誤譯了
A freeze action on a final field f of an object o takes place when a constructor for o in which f is written exits,either abruptly or normally
如果一個(gè)對象o的包含對final field的寫操作的構(gòu)造方法發(fā)生,一個(gè)對final field的一個(gè)固化操作將會被調(diào)用,即使這個(gè)構(gòu)造方法可以是突然的,或者正常的。
進(jìn)一步的解釋:
一些特殊的機(jī)制像反射,反序列化,是允許在構(gòu)造結(jié)束后修改final field的值的。像可以使用java.lang.reflect中的setX(...)方法來達(dá)到這個(gè)效果。當(dāng)這個(gè)fiedl是final的,這個(gè)方法只有滿足2個(gè)條件才能設(shè)置成功,否則會拋IllegalAccessException異常:這個(gè)field不是靜態(tài)的,并且對這個(gè)field設(shè)置setAccessible(true)成功。
已有 0 人發(fā)表留言,猛擊->>這里<<-參與討論
ItEye推薦