
????????到目前為止, 我們重點(diǎn)討論的是如何確保對(duì)象不被發(fā)布, 例如讓對(duì)象封閉在線(xiàn)程或另一個(gè)對(duì)象的內(nèi)部。 當(dāng)然,在某些情況下我們希望在多個(gè)線(xiàn)程間共享對(duì)象, 此時(shí)必須確保安全地進(jìn)行共享。 然而, 如果只是像程序清單 3-14 那樣將對(duì)象引用保存到公有域中, 那么還不足以安全地發(fā)布這個(gè)對(duì)象。

? ??????你可能會(huì)奇怪, 這個(gè)看似沒(méi)有問(wèn)題的示例何以會(huì)運(yùn)行失敗。由于存在可見(jiàn)性問(wèn)題, 其他線(xiàn)程看到的Holder 對(duì)象將處于不一致的狀態(tài), 即便在該對(duì)象的構(gòu)造函數(shù)中已經(jīng)正確地構(gòu)建了不變性條件。這種不正確的發(fā)布導(dǎo)致其他線(xiàn)程看到尚未創(chuàng)建完成的對(duì)象。
不正確性的發(fā)布
? ??????你不能指望一個(gè)尚未被完全創(chuàng)建的對(duì)象擁有完整性。某個(gè)觀察該對(duì)象的線(xiàn)程將看到對(duì)象處于不一致的狀態(tài), 然后看到對(duì)象的狀態(tài)突然發(fā)生變化, 即使線(xiàn)程在對(duì)象發(fā)布后還沒(méi)有修改過(guò)它。 事實(shí)上, 如果程序清單3-15中的Holder使用程序清單3-14中的不安全發(fā)布方式, 那么另一個(gè)線(xiàn)程在調(diào)用assertSanity時(shí)將拋出AssertionError 。

????????由于沒(méi)有使用同步來(lái)確保Holder對(duì)象對(duì)其他線(xiàn)程可見(jiàn), 因此將Holder稱(chēng)為“ 未被正確發(fā)布”。在未被正確發(fā)布的對(duì)象中存在兩個(gè)問(wèn)題。首先, 除了發(fā)布對(duì)象的線(xiàn)程外, 其他線(xiàn)程可以看到的Holder域是一個(gè)失效值, 因此將看到一個(gè)空引用或者之前的舊值。然而, 更糟糕的情況是, 線(xiàn)程看到Holder引用的值是最新的, 但Holder狀態(tài)的值卻是失效的。情況變得更加不可預(yù)測(cè)的是, 某個(gè)線(xiàn)程在第一次讀取域時(shí)得到失效值, 而再次讀取這個(gè)域時(shí)會(huì)得到一個(gè)更新值,這也是assertSainty拋出AssertionError的原因。
????????如果沒(méi)有足夠的同步, 那么當(dāng)在多個(gè)線(xiàn)程間共享數(shù)據(jù)時(shí)將發(fā)生一些非常奇怪的事情。
不可變對(duì)象與初始化安全性
????????由于不可變對(duì)象是一種非常重要的對(duì)象, 因此Java內(nèi)存模型為不可變對(duì)象的共享提供一種特殊的初始化安全性保證。我們已經(jīng)知道, 即使某個(gè)對(duì)象的引用對(duì)其他線(xiàn)程是可見(jiàn)的,也并不意味著對(duì)象狀態(tài)對(duì)于使用該對(duì)象的線(xiàn)程來(lái)說(shuō)一定是可見(jiàn)的。為了確保對(duì)象狀態(tài)能呈現(xiàn)出一致的視圖, 就必須使用同步。
????????另一方面,即使在發(fā)布不可變對(duì)象的引用時(shí)沒(méi)有使用同步, 也仍然可以安全地訪(fǎng)問(wèn)該對(duì)象。為了維持這種初始化安全性的保證, 必須滿(mǎn)足不可變性的所有需求:狀態(tài)不可修改, 所有域都是final類(lèi)型, 以及正確的構(gòu)造過(guò)程。(如果程序清單3-15中的Holder對(duì)象是不可變的,那么即使Holder 沒(méi)有被正確地發(fā)布, 在assertSanity中也不會(huì)拋出Asserti.onError。)
? ??????任何線(xiàn)程都可以在不需要額外同步的情況下安全地訪(fǎng)問(wèn)不可變對(duì)象,即使在發(fā)布這些對(duì)象時(shí)沒(méi)有使用同步。
????????這種保證還將延伸到被正確創(chuàng)建對(duì)象中所有 final 類(lèi)型的域。在沒(méi)有額外同步的情況下,也可以安全地訪(fǎng)問(wèn) final類(lèi)型的域。然而,如果final 類(lèi)型的域所指向的是可變對(duì)象,那么在訪(fǎng)問(wèn)這些域所指向的對(duì)象的狀態(tài)時(shí)仍然需要同步。
安全發(fā)布的常用模式
????????可變對(duì)象必須通過(guò)安全的方式來(lái)發(fā)布,這通常意味若在發(fā)布和使用該對(duì)象的線(xiàn)程時(shí)都必須使用同步?,F(xiàn)在,我們將重點(diǎn)介紹如何確保使用對(duì)象的線(xiàn)程能夠看到該對(duì)象處于已發(fā)布的狀態(tài),并稍后介紹如何在對(duì)象發(fā)布后對(duì)其可見(jiàn)性進(jìn)行修改。
????????要安全地發(fā)布一個(gè)對(duì)象,對(duì)象的引用以及對(duì)象的狀態(tài)必須同時(shí)對(duì)其他線(xiàn)程可見(jiàn)。一個(gè)正確構(gòu)造的對(duì)象可以通過(guò)以下方式來(lái)安全地發(fā)布:
a.在靜態(tài)初始化函數(shù)中初始化一個(gè)對(duì)象引用。
b.將對(duì)象的引用保存到volatile 類(lèi)型的域或者AtomicReferance 對(duì)象中。
c.將對(duì)象的引用保存到某個(gè)正確構(gòu)造對(duì)象的final 類(lèi)型域中。
d.將對(duì)象的引用保存到一個(gè)由鎖保護(hù)的域中
????????在線(xiàn)程安全容器內(nèi)部的同步意味著,在將對(duì)象放入到某個(gè)容器,例如Vector 或synchronizedList時(shí),將滿(mǎn)足上述最后一條需求。如果線(xiàn)程A將對(duì)象X放入一個(gè)線(xiàn)程安全的容器,隨后線(xiàn)程B讀取這個(gè)對(duì)象,那么可以確保B看到A設(shè)置的X 狀態(tài),即便在這段讀/寫(xiě)X的應(yīng)用程序代碼中沒(méi)有包含顯式的同步。盡管Javadoc 在這個(gè)主題上沒(méi)有給出很清晰的說(shuō)明,但線(xiàn)程安全庫(kù)中的容器類(lèi)提供了以下的安全發(fā)布保證:
a.通過(guò)將一個(gè)鍵或者值放入Hashtable、synchronizedMap 或者ConcurrentMap 中,可以安全地將它發(fā)布給任何從這些容器中訪(fǎng)問(wèn)它的線(xiàn)程(無(wú)論是直接訪(fǎng)問(wèn)還是通過(guò)迭代器訪(fǎng)問(wèn)。
b.通過(guò)將某個(gè)元素放入Vector 、CopyOnWriteArrayList、CopyOnWriteArraySet 、synchronizedList或synchronizedSet 中,可以將該元素安全地發(fā)布到任何從這些容器中訪(fǎng)問(wèn)該元素的線(xiàn)程。
c.通過(guò)將某個(gè)元素放入BlockingQueue 或者ConcurrentLinkedQueue 中,可以將該元素安全地發(fā)布到任何從這些隊(duì)列中訪(fǎng)問(wèn)該元素的線(xiàn)程。
????????類(lèi)庫(kù)中的其他數(shù)據(jù)傳遞機(jī)制(例如Future 和Exchanger) 同樣能實(shí)現(xiàn)安全發(fā)布,在介紹這些機(jī)制時(shí)將討論它們的安全發(fā)布功能。
????????通常,要發(fā)布一個(gè)靜態(tài)構(gòu)造的對(duì)象,最簡(jiǎn)單和最安全的方式是使用靜態(tài)的初始化器:
public static Holder holder= new Holder(42);
????????靜態(tài)初始化器由JVM在類(lèi)的初始化階段執(zhí)行。由于在JVM內(nèi)部存在著同步機(jī)制,因此通過(guò)這種方式初始化的任何對(duì)象都可以被安全地發(fā)布[JLS 12.4.2] 。
事實(shí)不可變對(duì)象
????????如果對(duì)象在發(fā)布后不會(huì)被修改, 那么對(duì)于其他在沒(méi)有額外同步的情況下安全地訪(fǎng)問(wèn)這些對(duì)象的線(xiàn)程來(lái)說(shuō), 安全發(fā)布是足夠的。所有的安全發(fā)布機(jī)制都能確保, 當(dāng)對(duì)象的引用對(duì)所有訪(fǎng)問(wèn)該對(duì)象的線(xiàn)程可見(jiàn)時(shí), 對(duì)象發(fā)布時(shí)的狀態(tài)對(duì)于所有線(xiàn)程也將是可見(jiàn)的, 井且如果對(duì)象狀態(tài)不會(huì)再改變, 那么就足以確保任何訪(fǎng)問(wèn)都是安全的。
????????如果對(duì)象從技術(shù)上來(lái)看是可變的, 但其狀態(tài)在發(fā)布后不會(huì)再改變, 那么把這種對(duì)象稱(chēng)為“事實(shí)不可變對(duì)象(Effectively Immutable Object)"。這些對(duì)象不需要滿(mǎn)足3.4節(jié)中提出的不可變性的嚴(yán)格定義。在這些對(duì)象發(fā)布后, 程序只需將它們視為不可變對(duì)象即可。通過(guò)使用事實(shí)不可變對(duì)象, 不僅可以簡(jiǎn)化開(kāi)發(fā)過(guò)程, 而且還能由于減少了同步而提高性能。
????????在沒(méi)有額外的同步的情況下,任何線(xiàn)程都可以安全地使用被安全發(fā)布的事實(shí)不可變對(duì)象。
可變對(duì)象
????????如果對(duì)象在構(gòu)造后可以修改, 那么安全發(fā)布只能確?!鞍l(fā)布當(dāng)時(shí)” 狀態(tài)的可見(jiàn)性。對(duì)于可變對(duì)象, 不僅在發(fā)布對(duì)象時(shí)需要使用同步, 而且在每次對(duì)象訪(fǎng)問(wèn)時(shí)同樣需要使用同步來(lái)確保后續(xù)修改操作的可見(jiàn)性。要安全地共享可變對(duì)象, 這些對(duì)象就必須被安全地發(fā)布, 并且必須是線(xiàn)程安全的或者由某個(gè)鎖保護(hù)起來(lái)。

安全地共享對(duì)象
????????當(dāng)獲得對(duì)象的一個(gè)引用時(shí), 你需要知道在這個(gè)引用上可以執(zhí)行哪些操作。在使用它之前是否需要獲得一個(gè)鎖?是否可以修改它的狀態(tài), 或者只能讀取它?許多并發(fā)錯(cuò)誤都是由于沒(méi)有理解共享對(duì)象的這些“既定規(guī)則”而導(dǎo)致的。當(dāng)發(fā)布一個(gè)對(duì)象時(shí),必須明確地說(shuō)明對(duì)象的訪(fǎng)問(wèn)方式。
