同步:
- 原子性
- 內(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ì)象。