要編寫線程安全的代碼鸽心,核心在于對狀態(tài)訪問操作進行管理,特別是共享的(Shared)和可變的(Mutable)訪問
對象的狀態(tài)指存儲在狀態(tài)變量(實例變量居暖、靜態(tài)域)中的數(shù)據(jù),對象的狀態(tài)可能包括其他依賴對象的域
“共享”意味著變量可以由多個線程訪問藤肢;“可變”意味著變量的值在其聲明周期內(nèi)可以發(fā)生變化
多個線程訪問同一個可變的狀態(tài)變量沒有使用合適的同步太闺,就可能出現(xiàn)錯誤,有三種方式可以修復(fù)這個問題
a.不在線程之間共享該狀態(tài)變量
b.將該狀態(tài)變量修改為不可變的變量
c.在使用狀態(tài)變量時使用同步
2.1 什么是線程安全性
正確性:某個類的行為與其規(guī)范完全一致
當多個線程訪問某個類時嘁圈,不管運行環(huán)境采用何種調(diào)用方式或者這些線程如何交替執(zhí)行省骂,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同蟀淮,這個類都能表現(xiàn)出正確的行為,那么就稱這個類是線程安全的
無狀態(tài)類一定是線程安全的
2.2 原子性
2.2.1競態(tài)條件
由于不恰當?shù)膱?zhí)行時序而出現(xiàn)的不正確的結(jié)果的情況稱為競態(tài)條件
最常見的競態(tài)條件類型就是“先檢查后執(zhí)行(Check-then-Act)”
“讀取-修改-寫入”操作也是一種競態(tài)條件
2.2.2延遲初始化中的競態(tài)條件
2.2.3復(fù)合操作
原子操作:對于訪問同一個狀態(tài)的所有操作(包括操作本身)來說钞澳,這個操作是一個以原子方式執(zhí)行的操作
復(fù)合操作:包含了一組必須以原子方式執(zhí)行的操作以保證線程安全性
當在無狀態(tài)的類中加入一個狀態(tài)時怠惶,如果該狀態(tài)完全由線程安全的對象管理,那么這個類仍舊是線程安全的
2.3加鎖機制
要保證狀態(tài)一致性轧粟,就需要單個原子操作中更新所有相關(guān)的狀態(tài)變量
2.3.1內(nèi)置鎖
同步代碼塊(Synchronized Block)
每個Java對象都可以用作一個實現(xiàn)同步的鎖策治,這些鎖被稱為內(nèi)置鎖(Intrinsic Lock)或者監(jiān)視器鎖(Monitor Lock)
內(nèi)置鎖是一種互斥鎖,最多只有一個線程持有這個鎖
2.3.2重入
重入意味著獲取鎖的操作的粒度是“線程”兰吟,而不是“調(diào)用”
重入提升了加鎖行為的封裝性通惫,因此簡化了面向?qū)ο蟛l(fā)代碼的執(zhí)行
2.4用鎖來保護狀態(tài)
鎖能夠使其被保護的對象以串行方式來執(zhí)行
一種常見的加鎖機制:將所有可變狀態(tài)都封裝在對象內(nèi)部,并通過對象的內(nèi)置鎖對所有訪問可變狀態(tài)的代碼路徑進行同步混蔼,使得該對象上不會發(fā)生并發(fā)訪問
對于每個包含多個變量的不變性條件履腋,其中涉及的所有變量都需要由同一個鎖來保護
2.5活躍性與性能
通常,在簡單性與性能之間存在著相互制約因素惭嚣,但是實現(xiàn)某個同步策略時遵湖,一定不要為了性能而犧牲簡單性(這可能會破壞安全性)
當執(zhí)行時間較長的計算或者可能無法快速完成的操作(例如網(wǎng)絡(luò)I/O或者控制臺I/O),一定不要持有鎖