線程安全性

什麼是線程安全性

        安全性實際上接近於“所見即所知”的狀態,就是在單線程和多線程中都不會被開發者不可見的因素改變其狀態。

        在線程安全類中封裝了必要的同步機制,因此客戶端不需要進一步採取同步措施。

        如果一個類沒有攜帶任何屬性,那麼它一定是線程安全的。大多數的Servlet都是線程安全的。

        狀態,說的明顯點就是對象的屬性。屬性的變化只能依靠類中能改變這些屬性的方法。

原子性

        原子操作,就是在執行某個代碼或代碼快時,對其所持有的變量,其它的線程無法訪問並修改。換句話說,原子操作的代碼或代碼塊,同一時間只能在一個線程中執行。

競態條件

        在併發線程中,由於不恰當的執行時序導致不正確的結果,這種現象成爲競態條件。常見的靜態條件例子就是count++,看似是一個原子操作,事實上它包含了三個狀態,讀取--改變--寫入,併發線程時,如果在一個線程在未執行寫入前另一個線程執行讀取,這就是競態條件。不存在競態條件的操作可以認爲是原子操作。

        競態條件最典型的模型是“先檢查,後執行”,首先,你在檢查某個狀態A,假設A爲真時,你去執行一個操作,操作完成後得到某個結果,現在問題是,你可能在執行操作過程中,A已經變成了假,因此得到的結果反而會帶來不可預料的錯誤。

        單例模式中的延遲初始化(對象在被使用時才被初始化,且只被初始化一次)會導致競態條件。比如如下代碼:

1
2
3
4
5
6
7
8
publicclassA{
privateExampleinstance=null;
publicExamplegetInstance(){
if(instance==null)
instance=newExample();//
returninstance;
}
}

複合操作

        多個原子操作的組合操作便構成了一個複合操作,複合操作容易帶來安全性問題,所以在count++可以替換爲原子操作的AtomicLong.incrementAndGet(1)。在java.util.concurrent.atomic包中封裝了一些原子變量類,用來執行原子操作。              

加鎖機制

內置鎖

        內置鎖(監視鎖)是互斥鎖的一種,通過持有某個對象(或者類)來使其保護的代碼快達到原子操作的目的。值得注意的是,內置鎖只能鎖代碼塊,不能鎖屬性和變量。而且鎖旗標一定是一個對象。

        被加鎖的代碼塊,只要執行完畢就會釋放鎖旗標,不管是正常執行完畢還是拋出異常中斷執行。

        鎖可以由一個對象充當,也可以由一個類的所有對象充當,如下演示了不同鎖旗標的內置鎖的實現。

1
2
3
4
5
6
7
8
staticsynchronizedvoidmethod(){...}/*staticsynchronized,*/
synchronized(Example.class)
synchronizedvoidmethod(){...}/*staticsynchronized,*/
synchronized(Example.this)
synchronized(obj)/**/

重入

        簡單的說,重入的意思就是持有鎖的東西是線程,而非某個類的某個對象。重入的好處就是一個線程請求自己已經獲得的鎖旗標時,這個請求會成功。重入有點類似與多重加鎖,但是鎖的鑰匙只有一個。實現重入的一種方法是,爲鎖關聯一個計數器,當計數器爲0時,說明鎖旗標未被任何線程佔據,此時才能被線程請求。當被同一線程重入時,每重入一次,計數器加一,每釋放一次,計數器減一。 減到0後,釋放鎖旗標。其他線程又能請求。

        以下例子將演示在重寫父類同步方法時的重入的重要性。

1
2
3
4
5
6
7
8
9
10
classFather{
publicsynchronizedvoidmethod(){...};
}
classSonextendsFather{
@override
publicsynchronizedvoidmethod(){
...
super.method();
};
}

         線程執行子類的method時,獲得this的鎖旗標,在執行super.method時,依然請求this的鎖旗標,如果沒有重入,那麼this的鎖旗標一直被佔據,spuer.method一直無法獲得鎖旗標,而Son的method方法永遠不會執行完畢,那麼鎖一直不會釋放,這樣就一直死鎖。      

鎖來保護狀態

        如果需要鎖來保護某個對象的狀態,那麼在每個可能使用到該對象的地方,都需要加鎖。很顯然這不實際。顯式聲明鎖的方法並不適合大型程序編寫。加鎖的恰當方式是,在對象所在的類中就對操作其狀態的方法進行加鎖。在擴展這個類時,記得在擴展方法上加鎖,否則很容易破壞類的加鎖協議。

        對於一個對象來說,每個共享和可變的變量都應該由一種鎖來保護。

        安全對象的操作可以說是安全的,但是多個安全對象的操作合在一起就不是線程安全的了。

        最後補充一下,當執行耗時操作(IO)時,千萬不要持有鎖,否則會出現糟糕的體驗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章