Java線程安全性

1.什麼是線程安全性

當多個線程訪問某個類時,不管運行時環境採用何種調度方式或者這些線程將如何交替執行,並且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行爲,那麼就稱這個類時線程安全的。

無狀態對象(既不包含任何域,也不包含任何對其他類中域的引用。計算過程中的臨時狀態僅存在與線程棧上的局部變量中,並且只能由正在執行的線程訪問)一定是線程安全的。


2.原子性

2.1 競態條件

當某個計算的正確性取決於多個線程的交替執行時序時,就會發生競態條件

2.2 複合操作

要避免競態條件問題,就必須在某個線程修改該變量時,通過某種方式防止其他線程使用這個變量。

假定有兩個操作A和B,如果從執行A的線程來看,當另一個線程執行B時,要麼將B全部執行完,要麼完全不執行B。那麼A和B對彼此來說是原子的。

原子操作是指,對於訪問同一個狀態的所有操作(包括該操作本身)來說,這個操作是一個以原子方式執行的操作。


當在無狀態的類中添加一個狀態時,如果該狀態完全由線程安全的對象來管理,那麼這個類仍然是線程安全的。當狀態變量的數量由一個變成多個時,並不會像狀態變量數量由零個變爲一個那麼簡單。


3.加鎖機制

在線程安全性的定義中要求,多個線程之間的操作無論採用何種執行時序或交替方式,都要保證不變性條件不被破壞。當在不變性條件中涉及多個變量時,各個變量之間並不是彼此獨立的,而是某個變量的值會對其他變量的值產生約束。因此,當更新某一個變量時,需要在同一個原子操作中對其他變量同時進行更新。

要保持狀態的一致性,就需要在單個原子操作中更新所有相關的狀態變量

3.1內置鎖

Java提供了一種內置的鎖機制來支持原子性:同步代碼塊(Synchronized Block)同步代碼塊包括兩部分:一個作爲鎖的對象引用,一個作爲由這個鎖保護的代碼塊。以關鍵字synchronized來修飾的方法就是一種橫跨整個方法體的同步代碼塊,其中該同步代碼塊的鎖就是方法調用所在的對象。靜態的synchronized方法以class對象作爲鎖。

每個java對象都可以用做一個實現同步的鎖,這些鎖被稱爲內置鎖(Intrinsic Lock)或監視器鎖(Monitor Lock)。線程在進入同步代碼塊之前會自動獲得鎖,並且在退出同步代碼塊時自動釋放鎖。

獲得內置鎖的唯一途徑就是進入由這個鎖保護的同步代碼塊或方法。

Java的內置鎖相當於一種互斥體(或互斥鎖),這意味着最多隻有一個線程能持有這種鎖。

3.2 重入

由於內置鎖是可重入的,因此如果某個線程試圖獲得一個已經由它自己持有的鎖,那麼這個請求就會成功


4.用鎖來保護狀態

訪問共享狀態的複合操作,都必須是原子操作以避免產生競態條件。如果在複合操作的執行過程中持有一個鎖,那麼會使複合操作成爲原子操作。

如果用同步來協調對某個變量的訪問,那麼訪問這個變量的所有位置上都需要使用同步。而且當使用鎖來協調對某個變量的訪問時,在訪問變量的所有位置上都要使用同一個鎖。

對於可能被多個線程同時訪問的可變狀態變量,在訪問它時都需要持有同一個鎖,在這種情況下,我們稱狀態變量是由這個鎖保護的。

當獲取與對象關聯的鎖時,並不能阻止其他線程訪問該對象,某個線程在獲得對象的鎖之後,只能阻止其他線程獲得同一個鎖。之所以每個對象都有一個內置鎖,只是爲了免去顯式的創建鎖對象。

只有被多個線程同時訪問的可變數據才需要通過鎖來保護。

當類的不變性條件涉及多個狀態變量時,那麼還有另外一個需求:在不變性條件中的每個變量都必須由同一個鎖來保護。


5.活躍性與性能

在簡單性與性能之間存在着相互制約因素。當實現某個同步策略時,一定不要盲目地爲了性能而犧牲簡單性(這可能會破壞安全性)

當使用鎖時,你應該清楚代碼塊中實現的功能,以及在執行該代碼塊時是否需要很長的時間。無論是執行計算密集的操作,還是在執行某個可能阻塞的操作,如果持有鎖的時間過長,那麼都會帶來活躍性或性能問題。


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