Java并發(fā)編程實(shí)戰(zhàn) Chapt3 對(duì)象的共享

同步:

  • 原子性
  • 內(nèi)存可見性

3.1 可見性

重排序(Reordering):在沒有同步的情況下姻蚓,編譯器、處理器以及運(yùn)行時(shí)等都可能對(duì)操作的執(zhí)行順序進(jìn)行一些意想不到的調(diào)整括勺。
只要有數(shù)據(jù)在多個(gè)線程之間共享宇植,就使用正確的同步兑徘。

3.1.1 失效數(shù)據(jù)

3.1.2 非原子的64位操作

最低安全性:當(dāng)線程在沒有同步的情況下讀取變量時(shí)吱七,可能會(huì)得到一個(gè)失效值,但至少這個(gè)值是由之前某個(gè)線程設(shè)置的值鹤竭,而不是一個(gè)隨機(jī)值踊餐。
最低安全性不適用于非volatile類型的64位數(shù)值變量(double和long)。變量的讀取和寫入操作都必須是原子操作臀稚,但對(duì)于非volatile類型的long和double變量吝岭,JVM允許將64位的讀操作或?qū)懖僮鞣纸鉃閮蓚€(gè)32位的操作。當(dāng)讀取一個(gè)非volatile類型的long變量時(shí)吧寺,如果對(duì)該變量的讀操作和寫操作在不同的線程中執(zhí)行窜管,那么很可能會(huì)讀取到某個(gè)值的高32位和另一個(gè)值的低32位。

3.1.3 加鎖和可見性

加鎖的含義不僅僅局限于互斥行為稚机,還包括內(nèi)存可見性幕帆。為了確保所有線程都能看到共享變量的最新值,所有執(zhí)行讀操作或者寫操作的線程都必須在同一個(gè)鎖上同步赖条。

3.1.4 volatile變量

當(dāng)把變量聲明為volatile類型后失乾,編譯器與運(yùn)行時(shí)(虛擬機(jī))都會(huì)注意到這個(gè)變量是共享的,因此不會(huì)將該變量上的操作與其他內(nèi)存操作一起重排序纬乍。volatile變量不會(huì)被緩存在寄存器或者對(duì)其他處理器不可見的地方碱茁,因此在讀取volatile類型的變量時(shí)總會(huì)返回最新的寫入值。
在訪問volatile變量時(shí)不會(huì)執(zhí)行加鎖操作仿贬,因此也就不會(huì)使執(zhí)行線程阻塞纽竣,因此volatile變量是一種比synchronized關(guān)鍵字更輕量級(jí)的同步機(jī)制。
寫入volatile變量相當(dāng)于退出同步塊茧泪,而讀取volatile變量相當(dāng)于進(jìn)入同步塊蜓氨。
不建議過度依賴volatile變量提供的可見性,如果在代碼中依賴volatile變量來控制狀態(tài)的可見性调炬,通常比使用鎖的代碼更脆弱语盈,也更難以理解。
僅當(dāng)volatile變量能簡(jiǎn)化代碼的實(shí)現(xiàn)以及對(duì)同步策略的驗(yàn)證時(shí)缰泡,才應(yīng)該使用它們刀荒。如果在驗(yàn)證正確性時(shí)需要對(duì)可見性進(jìn)行復(fù)雜的判斷代嗤,那么就不要使用volatile變量。volatile變量的正確使用方式包括:確保它們自身狀態(tài)的可見性缠借,確保它們所引用對(duì)象的狀態(tài)的可見性干毅,以及標(biāo)識(shí)一些重要的程序生命周期事件的發(fā)生。
典型用法:


加鎖機(jī)制既可以確逼梅担可見性又可以確保原子性硝逢,而volatile變量只能確保可見性绅喉。
當(dāng)且僅當(dāng)滿足以下所有條件時(shí)渠鸽,才應(yīng)該使用volatile變量:**

  • 對(duì)變量的寫入操作不依賴變量的當(dāng)前值,或者你能確保只有單個(gè)線程更新變量的值柴罐。(非競(jìng)爭(zhēng)條件)
  • 該變量不會(huì)與其他狀態(tài)變量一起納入不變性條件中徽缚。(非復(fù)合操作)
  • 在訪問變量時(shí)不需要加鎖。

3.2 發(fā)布與逸出

發(fā)布:使對(duì)象能夠在當(dāng)前作用域之外的代碼中使用
逸出:某個(gè)不應(yīng)該發(fā)布的對(duì)象被發(fā)布
發(fā)布對(duì)象:

  • 將對(duì)象的引用保存到一個(gè)公有的靜態(tài)變量中
  • 發(fā)布一個(gè)對(duì)象時(shí)革屠,在該對(duì)象的非私有域中引用的所有對(duì)象同樣會(huì)被發(fā)布
  • 發(fā)布一個(gè)內(nèi)部類的實(shí)例(內(nèi)部類持有外部類的引用)

安全的對(duì)象構(gòu)造過程

當(dāng)從對(duì)象的構(gòu)造函數(shù)中發(fā)布對(duì)象時(shí)凿试,只是發(fā)布了一個(gè)尚未構(gòu)造完成的對(duì)象。即使發(fā)布對(duì)象的語句位于構(gòu)造函數(shù)的最后一行也是如此似芝。如果this引用在構(gòu)造過程中逸出那婉,那么這種對(duì)象就被認(rèn)為是不正確構(gòu)造。
不要在構(gòu)造過程中使this引用逸出党瓮。
在構(gòu)造函數(shù)中調(diào)用一個(gè)可改寫的實(shí)例方法時(shí)(非private详炬,非final),同樣會(huì)導(dǎo)致this引用在構(gòu)造過程中逸出麻诀。
如果想在構(gòu)造函數(shù)中注冊(cè)一個(gè)事件監(jiān)聽器或啟動(dòng)線程痕寓,那么可以使用一個(gè)私有的構(gòu)造函數(shù)和一個(gè)公共的工廠方法,從而避免不正確的構(gòu)造過程蝇闭。

3.3 線程封閉

線程封閉:僅在單線程內(nèi)訪問數(shù)據(jù)
Java語言及其核心庫(kù)提供了一些機(jī)制來幫助維持線程封閉性呻率,例如局部變量和ThreadLocal類,但即便如此呻引,程序員仍然需要負(fù)責(zé)確保封閉在線程中的對(duì)象不會(huì)從線程中逸出礼仗。**

3.3.1 Ad-hoc線程封閉

Ad-hoc線程封閉:維護(hù)線程封閉性的職責(zé)完全由程序?qū)崿F(xiàn)來承擔(dān)
當(dāng)決定使用線程封閉技術(shù)時(shí),通常是因?yàn)橐獙⒛硞€(gè)特定的子系統(tǒng)實(shí)現(xiàn)為一個(gè)單線程子系統(tǒng)逻悠。在某些情況下元践,單線程子系統(tǒng)提供的簡(jiǎn)便性要?jiǎng)龠^Ad-hoc線程封閉性技術(shù)的脆弱性。
只要確保只有單個(gè)線程對(duì)共享的volatile變量執(zhí)行寫入操作童谒,那么就可以安全地在這些共享的volatile變量上執(zhí)行“讀取-修改-寫入”的操作单旁。這相當(dāng)于將修改操作封閉在單個(gè)線程中以防止發(fā)生競(jìng)爭(zhēng)條件,并且volatile變量的可見性保證還確保了其他線程能看到最新的值饥伊。**
由于Ad-hoc線程封閉技術(shù)的脆弱性象浑,因此在程序中盡量少用它蔫饰,在可能的情況下,應(yīng)該使用更強(qiáng)的線程封閉技術(shù)(棧封閉或ThreadLocal類)愉豺。**

3.3.2 棧封閉

在棧封閉中篓吁,只能通過局部變量才能訪問對(duì)象。
局部變量的固有屬性之一就是封閉在執(zhí)行線程中蚪拦。它們位于執(zhí)行線程的棧中杖剪,其他線程無法訪問這個(gè)棧。
棧封閉比Ad-hoc線程封閉更易于維護(hù)驰贷,也更加健壯盛嘿。
基本類型的局部變量始終封閉在線程內(nèi)。
如果線程內(nèi)部上下文中使用非線程安全的對(duì)象括袒,那么該對(duì)象仍然是線程安全的孩擂。**

3.3.3 ThreadLocal類

ThreadLocal對(duì)象通常用于防止對(duì)可變的單實(shí)例變量或全局變量進(jìn)行共享。
當(dāng)某個(gè)頻繁執(zhí)行的操作需要一個(gè)臨時(shí)對(duì)象箱熬,例如一個(gè)緩沖區(qū),而同時(shí)又希望避免在每次執(zhí)行時(shí)都重新分配該臨時(shí)對(duì)象狈邑,就可以使用這項(xiàng)技術(shù)城须。
ThreadLocal變量類似于全局變量,它能降低代碼的可重用性米苹,并在類之間引入隱含的耦合性糕伐,因此在使用時(shí)要格外小心。

3.4 不變性

不可變對(duì)象一定是線程安全的蘸嘶。
當(dāng)滿足以下條件時(shí)良瞧,對(duì)象才是不可變的:

  • 對(duì)象創(chuàng)建以后其狀態(tài)就不能修改
  • 對(duì)象的所有域都是final類型(從技術(shù)上看,不可變對(duì)象并不需要將其所有的域都聲明為final類型)
  • 對(duì)象是正確創(chuàng)建的(在對(duì)象創(chuàng)建期間训唱,this引用沒有逸出)

在不可變對(duì)象的內(nèi)部仍可以使用可變對(duì)象來管理它們的狀態(tài)褥蚯。

3.4.1 final域

在java內(nèi)存模型中,final域能確保初始化過程的安全性况增,從而可以不受限制地訪問不可變對(duì)象赞庶,并在共享這些對(duì)象時(shí)無須同步。
即使對(duì)象是可變地澳骤,通過將對(duì)象的某些域聲明為final類型歧强,仍然可以簡(jiǎn)化對(duì)狀態(tài)的判斷。
除非某個(gè)域是可變的为肮,否則將其聲明為final域摊册。

3.4.2 示例:使用volatile類型來發(fā)布不可變對(duì)象

每當(dāng)需要對(duì)一組相關(guān)數(shù)據(jù)以原子方式執(zhí)行某個(gè)操作時(shí),就可以考慮創(chuàng)建一個(gè)不可變的類來包含這些數(shù)據(jù)颊艳。**
對(duì)于在訪問和更新多個(gè)相關(guān)變量時(shí)出現(xiàn)的競(jìng)爭(zhēng)條件問題茅特,可以通過將這些變量全部保存在一個(gè)不可變對(duì)象中來消除忘分。
如果是一個(gè)可變的對(duì)象,那么就必須使用鎖來確保原子性温治。如果是一個(gè)不可變對(duì)象饭庞,那么當(dāng)線程獲得了對(duì)該對(duì)象的引用后,就不必?fù)?dān)心另一個(gè)線程會(huì)修改對(duì)象的狀態(tài)熬荆。



如果要更新這些變量舟山,那么可以創(chuàng)建一個(gè)新的容器對(duì)象,但其他使用原有對(duì)象的線程仍然會(huì)看到對(duì)象處于一致的狀態(tài)卤恳。**
通過使用包含多個(gè)狀態(tài)變量的容器對(duì)象(不可變對(duì)象)來維持不變性條件搪哪,并使用一個(gè)volatile類型的引用來確保可見性鹦聪,使得在沒有顯式地使用鎖的情況下仍然是線程安全的澡匪。**

3.5 安全發(fā)布

3.5.1 不正確地發(fā)布:正確的對(duì)象被破壞



沒有使用同步確保可見性

3.5.2 不可變對(duì)象與初始化安全性

由于不可變對(duì)象是一種非常重要的對(duì)象拆融,因此Java內(nèi)存模型為不可變對(duì)象的共享提供了一種特殊的初始化安全性保證蠢琳。
任何線程都可以在不需要額外同步的情況下安全地訪問不可變對(duì)象,即使在發(fā)布這些對(duì)象時(shí)沒有使用同步镜豹。
這種保證還將延伸到被正確創(chuàng)建對(duì)象中所有final類型的域傲须。在沒有額外同步的情況下,也可以安全地訪問final類型的域趟脂。然而泰讽,如果final類型的域所指向的是可變對(duì)象,那么在訪問這些域所指向的對(duì)象的狀態(tài)時(shí)仍然需要同步昔期。

3.5.3 安全發(fā)布的常用模式

可變對(duì)象在發(fā)布和使用的線程都必須使用同步已卸。
要安全地發(fā)布一個(gè)對(duì)象,對(duì)象的引用以及對(duì)象的狀態(tài)必須同時(shí)對(duì)其他線程可見硼一。一個(gè)正確構(gòu)造的對(duì)象可以通過以下方式來安全地發(fā)布:

  • 在靜態(tài)初始化函數(shù)中初始化一個(gè)對(duì)象引用累澡。
  • 將對(duì)象的引用保存到volatile類型的域或者AtomicReference對(duì)象中。
  • 將對(duì)象的引用保存到某個(gè)正確構(gòu)造對(duì)象的final類型域中欠动。
  • 將對(duì)象的引用保存到一個(gè)由鎖保護(hù)的域中(在線程安全容器內(nèi)部的同步)永乌。

要發(fā)送一個(gè)靜態(tài)構(gòu)造的對(duì)象,最簡(jiǎn)單和最安全的方式是使用靜態(tài)的初始化器具伍。


靜態(tài)初始化器由JVM在類的初始化階段執(zhí)行翅雏。由于在JVM內(nèi)部存在著同步機(jī)制,因此通過這種方式初始化的任何對(duì)象都可以被安全地發(fā)布人芽。

3.5.4 事實(shí)不可變對(duì)象

事實(shí)不可變對(duì)象(Effectively Immutable Object):對(duì)象從技術(shù)上來看是可變地望几,但其狀態(tài)在發(fā)布后不會(huì)再改變。
程序只需將之視為不可變對(duì)象即可萤厅。
在沒有額外同步地情況下橄抹,任何線程都可以安全地使用被安全發(fā)布的事實(shí)不可變對(duì)象靴迫。

3.5.5 可變對(duì)象

對(duì)象的發(fā)布需求取決于它的可變性:

  • 不可變對(duì)象可以通過任意機(jī)制來發(fā)布。
  • 事實(shí)不可變對(duì)象必須通過安全方式來發(fā)布楼誓。
  • 可變對(duì)象必須通過安全方式來發(fā)布玉锌,并且必須是線程安全的或者由某個(gè)鎖來保護(hù)起來。

3.5.6 安全地共享對(duì)象

當(dāng)發(fā)布一個(gè)對(duì)象時(shí)疟羹,必須明確地說明對(duì)象的訪問方式主守。

并發(fā)中使用和共享對(duì)象的實(shí)用策略:

  • 線程封閉。
  • 只讀共享榄融。共享的只讀對(duì)象包括不可變對(duì)象和事實(shí)不可變對(duì)象参淫。
  • 線程安全共享。線程安全的對(duì)象在其內(nèi)部實(shí)現(xiàn)同步愧杯,因此多個(gè)線程可以通過對(duì)象的公有接口來進(jìn)行訪問而不需要進(jìn)一步的同步涎才。
  • 保護(hù)對(duì)象。被保護(hù)的對(duì)象只能通過持有特定的鎖來訪問力九。保護(hù)對(duì)象包括封裝在其他線程安全對(duì)象中的對(duì)象耍铜,以及已發(fā)布的并且由某個(gè)特定鎖保護(hù)的對(duì)象。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末跌前,一起剝皮案震驚了整個(gè)濱河市业扒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舒萎,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹭沛,死亡現(xiàn)場(chǎng)離奇詭異臂寝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)摊灭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門咆贬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人帚呼,你說我怎么就攤上這事掏缎。” “怎么了煤杀?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵眷蜈,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我沈自,道長(zhǎng)酌儒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任枯途,我火速辦了婚禮忌怎,結(jié)果婚禮上籍滴,老公的妹妹穿的比我還像新娘。我一直安慰自己榴啸,他們只是感情好孽惰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鸥印,像睡著了一般勋功。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辅甥,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天酝润,我揣著相機(jī)與錄音,去河邊找鬼璃弄。 笑死要销,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的夏块。 我是一名探鬼主播疏咐,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼脐供!你這毒婦竟也來了浑塞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤政己,失蹤者是張志新(化名)和其女友劉穎酌壕,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體歇由,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卵牍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沦泌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糊昙。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谢谦,靈堂內(nèi)的尸體忽然破棺而出释牺,到底是詐尸還是另有隱情,我是刑警寧澤回挽,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布没咙,位于F島的核電站,受9級(jí)特大地震影響千劈,放射性物質(zhì)發(fā)生泄漏镜撩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望袁梗。 院中可真熱鬧宜鸯,春花似錦、人聲如沸遮怜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锯梁。三九已至即碗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陌凳,已是汗流浹背剥懒。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留合敦,地道東北人初橘。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像充岛,于是被迫代替她去往敵國(guó)和親保檐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容