4.1 設(shè)計(jì)線程安全的類
- 找出構(gòu)成對象狀態(tài)的所有變量
- 找出約束狀態(tài)變量的不變性條件
- 建立對象狀態(tài)的并發(fā)訪問管理策略
如果對象中的所有的域都是基本類型的變量,那么這些域?qū)?gòu)成對象的全部狀態(tài)
- 對于含有n個(gè)基本類型的對象,其狀態(tài)就是這些域構(gòu)成的n元組。
如果在對象的域中引用了其他對象征绎,那么該對象的狀態(tài)將包含被引用對象的域。
4.1.1 收集同步需求
對象與變量都有一個(gè)狀態(tài)空間,即所有可能的取值擒滑。狀態(tài)空間越小,就越容易判斷線程的狀態(tài)叉弦。final類型的域使用得越多丐一,就越能簡化對象可能狀態(tài)的分析過程。
不變性條件:用于判斷狀態(tài)是否有效
后驗(yàn)條件:用于判斷狀態(tài)遷移是否有效
如果某些狀態(tài)是無效的淹冰,那么必須對底層的變量進(jìn)行封裝库车,否則客戶代碼可能會(huì)使對象處于無效狀態(tài)。如果在某個(gè)操作中存在無效的狀態(tài)轉(zhuǎn)換樱拴,那么該操作必須是原子的柠衍。如果在一個(gè)不變性條件中包含多個(gè)變量,那么在執(zhí)行任何訪問相關(guān)變量的操作時(shí)晶乔,都必須持有保護(hù)這些變量的鎖珍坊。**
4.1.2 依賴狀態(tài)的操作
先驗(yàn)條件:在調(diào)用方法之前必須為真的條件
后驗(yàn)條件:方法順利執(zhí)行完畢之后必須為真的條件
依賴狀態(tài)的操作:操作中包含有基于狀態(tài)的先驗(yàn)條件
- eg. 不能從空隊(duì)列中移除一個(gè)元素
在單線程程序中,如果某個(gè)操作無法滿足先驗(yàn)條件正罢,那么就只能失敗阵漏。但在并發(fā)程序中,先驗(yàn)條件可能會(huì)由于其他線程執(zhí)行的操作而變成真腺怯。在并發(fā)程序中要一直等到先驗(yàn)條件為真袱饭,然后再執(zhí)行該操作。**
實(shí)現(xiàn):
- 阻塞隊(duì)列(BlockingQueue…)
- 信號量(Semaphore)
- …
4.1.3 狀態(tài)的所有權(quán)
4.2 實(shí)例封閉
當(dāng)一個(gè)對象被封裝到另一個(gè)對象中時(shí)呛占,能夠訪問被封裝對象的所有代碼路徑都是已知的虑乖。與對象可以由整個(gè)程序訪問的情況相比,更易于對代碼進(jìn)行分析晾虑。通過將封閉機(jī)制與合適的加鎖策略結(jié)合起來疹味,可以確保以線程安全的方式來使用非線程安全的對象。
將數(shù)據(jù)封裝在對象內(nèi)部帜篇,可以將數(shù)據(jù)的訪問限制在對象的方法上糙捺,從而更容易確保線程在訪問數(shù)據(jù)時(shí)總能持有正確的鎖。**
實(shí)例封閉是構(gòu)建線程安全類的一個(gè)最簡單方式笙隙。
實(shí)例封閉還使得不同的狀態(tài)變量可以由不同的鎖來保護(hù)洪灯。
Java的包裝器工廠(eg. Collections.synchronizedList),只要包裝器對象擁有對底層容器對象的唯一引用(即把底層容器對象封閉在包裝器中)竟痰,那么它就是線程安全的签钩。對底層容器對象的所有訪問必須通過包裝器來進(jìn)行掏呼。
當(dāng)發(fā)布其他對象時(shí),例如迭代器或內(nèi)部的類實(shí)例铅檩,可能會(huì)間接地發(fā)布被封閉對象憎夷,同樣會(huì)使被封閉對象逸出。
封閉機(jī)制更易于構(gòu)造線程安全的類昧旨,因?yàn)楫?dāng)封閉類的狀態(tài)時(shí)拾给,在分析類的線程安全性時(shí)就無須檢查整個(gè)程序。
4.2.1 Java監(jiān)視器模式
遵循Java監(jiān)視器模式的對象會(huì)把對象的所有可變狀態(tài)都封裝起來兔沃,并由對象自己的內(nèi)置鎖來保護(hù)蒋得。
Java監(jiān)視器模式的主要優(yōu)勢在于它的簡單性。細(xì)粒度的加鎖策略則可以提高伸縮性粘拾。
私有鎖
4.2.2 示例:車輛追蹤
4.3 線程安全性的委托
4.3.1 示例:基于委托的車輛追蹤器
4.3.2 獨(dú)立的狀態(tài)變量
多個(gè)變量之間是彼此獨(dú)立的窄锅,則可以將線程安全性委托給多個(gè)狀態(tài)變量。
組合而成的類不會(huì)在其包含的多個(gè)狀態(tài)變量上增加任何不變性條件缰雇。
4.3.3 當(dāng)委托失效時(shí)
如果某個(gè)類含有復(fù)合操作,那么僅靠委托不足以實(shí)現(xiàn)線程安全性追驴。在這種情況下械哟,這個(gè)類必須提供自己的加鎖機(jī)制以保證這些復(fù)合操作都是原子操作,除非整個(gè)復(fù)合操作都可以委托給狀態(tài)變量殿雪。
4.3.4 發(fā)布底層的狀態(tài)變量
如果一個(gè)狀態(tài)變量是線程安全的暇咆,并且沒有任何不變性條件來約束它的值,在變量的操作上也不存在任何不允許的狀態(tài)轉(zhuǎn)換丙曙,那么就可以安全地發(fā)布這個(gè)變量爸业。
4.3.5 示例:發(fā)布狀態(tài)的車輛追蹤器
4.4 在現(xiàn)有的線程安全類中添加功能
直接修改原始的類;
擴(kuò)展原始類
- 擴(kuò)展方法更加脆弱】髁現(xiàn)在同步策略被分布到多個(gè)單獨(dú)維護(hù)的源代碼文件中扯旷,如果底層的類改變了同步策略并選擇了不同的鎖來保護(hù)它的狀態(tài)變量,那么子類會(huì)被破壞索抓。
4.4.1 客戶端加鎖機(jī)制
第三種策略:擴(kuò)展類的功能
- 將擴(kuò)展代碼放入一個(gè)“輔助類”中
相比擴(kuò)展钧忽,客戶端加鎖更加脆弱。它將類C的加鎖代碼放到與C完全無關(guān)的其他類中逼肯。
4.4.2 組合
通過類似委托的方式來實(shí)現(xiàn)
額外的同步層可能導(dǎo)致輕微的性能損失耸黑,但更為健壯。
4.5 將同步策略文檔化
在文檔中說明客戶代碼需要了解的線程安全性保證篮幢,以及代碼維護(hù)人員需要了解的同步策略大刊。