線程安全篇A
其實,并發(fā)編程理論并不過多的涉及線程和鎖繁调,雖然構建并發(fā)程序需要正確的使用線程和鎖萨蚕,然而這只是內部機理帶來的手段而已;本質上來說蹄胰,寫出線程安全的程序岳遥,在于狀態(tài)的訪問管理,特別是共享和可變狀態(tài)裕寨。
一個對象的狀態(tài)是其數(shù)據浩蓉,它可以存儲在其實例或者靜態(tài)域中,這些數(shù)據也可以來自其它獨立的實例對象宾袜。對象的狀態(tài)捻艳,包括了那些所有會影響其外部可見行為的數(shù)據。
- 說到共享庆猫,意思是一個變量可以被多個線程訪問;
- 說到可變性认轨,意思是一個值可以在其生命周期內被改變。
我們談到線程安全似乎指的是代碼月培,但真正我們要做的是嘁字,保護數(shù)據不被不受控制的訪問影響昨稼。
對象是否需要設置成線程安全,在于其是否需要多線程訪問拳锚,程序如何使用對象假栓,決定了其是否需要線程安全,而不取決于這個對象能做什么霍掺。
Java中同步的一個基本機理是使用synchronized關鍵字匾荆,這能提供一個排它鎖;同時杆烁,同步也包含了volatile關鍵字牙丽,具體鎖以及原子變量。不遵從以上規(guī)則的看似穩(wěn)定的情況兔魂,總是存在一定的隱患烤芦。
多線程訪問可變狀態(tài)時,若沒有適合的同步操作析校,總是存在問題隱患构罗。有三個基本方式可以修復此類問題。
- 不要在線程間共享可變狀態(tài)
- 將狀態(tài)設置成不可變
- 無論何時訪問可變狀態(tài)智玻,都使用同步
設計一個線程安全的類要遠比將類重構成線程安全容易的多咬荷。
面向對象編程的一些技巧可以幫助更容易的創(chuàng)建線程安全類仅叫,比如封裝和數(shù)據隱藏。越少代碼訪問特定的變量,越容易將其同步杆查,越好的封裝喻圃,越容易實現(xiàn)線程安全崔步。
什么是線程安全
線程安全類的定義:當被多線程訪問時菲茬,它總能表現(xiàn)正確,而不管運行環(huán)境下多線程的調度或是線程的交叉執(zhí)行裹驰,并且不需要調用代碼進行額外的同步或其它協(xié)調操作隧熙。線程安全類封裝了一切需要的同步,因此客戶端并不需要自己提供這些邦马。
無狀態(tài)的對象總是線程安全的贱鼻,比如絕大多數(shù)的Servlet對象(Javaweb,例子省略)滋将,它并沒有成員變量邻悬,也沒有其它類成員的引用,它涉及的計算只存在于局部變量中随闽,而局部變量是線程私有的父丰,所以其它線程的操作并不會影響當前線程的計算或狀態(tài),即它是線程安全的。
原子性
原子操作:單一的蛾扇,不可被拆分的操作攘烛,這是相對所有操作而言的,其總是操作在同一個狀態(tài)上镀首。
比如
int i = 0;
++i;//op1
op1并不是一個原子操作坟漱,它是三個子操作的序列:獲取數(shù)據,修改數(shù)據更哄,將數(shù)據寫回芋齿。
這是一個讀-改-寫的操作,在某些場景下成翩,不同步這種操作會引發(fā)線程不安全的問題觅捆,如serlvet中統(tǒng)計訪問數(shù)。
- 競爭條件
競爭條件的出現(xiàn):當計算結果的正確性依賴于運行時的相對時間點或是線程的交叉調用麻敌。
最常見的競爭條件類型:檢查-執(zhí)行策略栅炒。這可能造成過期的檢查結果決定下一步的行為。
- 懶加載中的競爭條件
懶加載指的是直到真正使用一個對象時术羔,才去初始化它赢赊,并只初始化一次。未做同步處理的懶加載容易出現(xiàn)線程安全問題聂示。比如下面這個例子域携。
@NotThreadSafe
public class LazyInitRace {
private ExpensiveObject instance = null;
public ExpensiveObject getInstance() {
if (instance == null)
instance = new ExpensiveObject();
return instance;
}
}
class ExpensiveObject { }
首先,getInstance方法將檢查ExpensiveObject對象是否已經存在鱼喉,若是的話,直接返回實例對象趋观,若否的話扛禽,則創(chuàng)建一個實例返回。但是這里面是會受到競爭條件影響的皱坛。比如编曼,加入有A和B兩個線程同時訪問getInstance,A發(fā)現(xiàn)ExpensiveObject對象為null剩辟,于是開始創(chuàng)建它掐场,此時B也在檢查該對象是否為空,而這個結果是難以預測的贩猎,比如線程如何調度熊户,初始化占用的時間以及給成員instance賦值的時機等。而B檢查的結果吭服,又會帶來getInstance方法返回不同的結果嚷堡。
數(shù)字自增操作和對象懶加載操作都是一系列操作的序列,這會造成競爭條件的發(fā)生艇棕。因此蝌戒,為了避免競爭條件的發(fā)生串塑,這些操作需要一種方式來避免一個正在修改的狀態(tài)被其它線程訪問,即需要保證北苟,當要訪問或修改某個狀態(tài)時桩匪,可以發(fā)生在這個狀態(tài)修改前或是修改后,而不是在修改中友鼻。
其中的一種方式吸祟,就是將讀-改-寫(比如自增操作)以及檢查-執(zhí)行(比如懶加載)的操作變?yōu)樵有缘摹1热缱栽霾僮魈乙疲梢允褂胏oncurrent包中的原子型類AtomicLong屋匕。比如懶加載,可以使用加鎖同步的方式使其能夠被原子性的執(zhí)行借杰。
//待下篇