java併發編程(二)線程的安全性

線程安全的核心在於對狀態的訪問操作,特別是共享狀態和可變狀態。共享意味着可以同時被多個線程訪問,可變意味着生命週期內可以發生變化。
要是對象是線程安全的就要使用同步機制來保證對對象狀態的訪問。
java中主要的同步機制 包括關鍵字synchronized獨佔鎖,Lock顯式鎖,原子變量(amoticInteger等)等,當然也包括volatile類型變量,注意volatile類型變量並不能保證數據的原子性。
在項目開發過程中應遵循先保證代碼正確運行,然後再提高代碼的性能的原則。即便如此也要在測試或者應用需求告訴你必須提高性能,且測試結果表明優化能顯著提高性能時纔去優化。原因是因爲併發錯誤是非常難以重現和調試的,如果只是在某段不常用的代碼上得到性能的提升,很可能被程序運行時的失敗風險抵消。

什麼是線程安全性

當多個線程訪問某個類時,不管任何環境任何方式或者線程間如何交替運行,在調用者的代碼裏不需要任何處理,這個類都能正確的被執行。那麼這個類就被稱爲線程安全的。
無狀態對象一定是線程安全的,無狀態對象是指計算過程中不使用全局變量,計算過程中的全部變量都是局部變量,且未調用其他的非無狀態對象。正因爲如此在使用全局變量時多考慮是否必要,儘量多使用局部變量。

原子性

在併發編程中由於不正確的執行順序產生不正確的執行結果叫做競態條件反過來說就是當計算的正確性取決於執行順序時就會發生競態條件。
典型的競態條件就是“先檢查後執行”,比如飽漢單例模式,當調用對象時先檢查對象是否存在,而後創建對象。如果一個線程在創建對象成功前另一個對象檢查對象是否存在就會出現線程安全問題。
原子性操作是指對於同一個狀態的所有操作要麼全執行,要麼全不執行。

鎖機制

在一個對象內存在兩個全局變量,如果兩個變量都已經做了線程安全相關的處理,是否意味着這個對象一定是線程安全的呢?
答案是不是的,因爲這隻保證了兩個變量各自的原子性,並沒有保證整個對象的原子性,如果一個變量對另一個變量產生約束時,需要兩個變量同時更新才能保證整個操作的原子性。
java提供了內置鎖來處理這種情況,使用synchronized修飾方法加鎖保證整個方法的原子性。
java內置鎖是一種互斥鎖,意味着同一時間只能有一個線程持有這種鎖。
併發中的原子性的含義和事物中的原子性相同,一組語句作爲一個不可分割的單元執行。
重入是當某個線程試圖獲取一個已經由他持有的鎖,那麼請求會成功。也就意味着重入鎖的獲取鎖操作的粒度是線程。
典型的重入情況,當子類重寫父類的synchronized方法,然後調用父類的這個方法,因爲不論調用哪個方法都會先調用父類的方法,所以當調用子類方法時就已經獲取了父類的鎖,這時再調用父類的方法不會死鎖。
可重入,就是可以重複獲取相同的鎖,比如一段代碼內鎖內嵌套這鎖,鎖都是this那麼當線程獲取了外層鎖時可以直接使用嵌套的鎖。

將一段複合操作的執行過程加鎖會使這段複合操作稱爲原子操作,但是僅僅如此是不夠的,如果對象的全局變量有被其他線程調用的話 對象依然是不安全的,必須要處理這些變量的同步問題,雖然大多數類都是通過加鎖來保證變量的原子性,但是這並不是要求一定要用鎖來保證變量的原子性。例如java的atomic包也可以實現同樣的效果。

活躍性與性能

當對某一個方法加了synchronied鎖以後,這個方法就只能同時被一個線程調用。如果這個方法耗時很長,導致大量線程等待,我們將這種程序稱爲不良併發程序
所以在實際開發中我們要儘量的縮小同步代碼塊的作用範圍,儘量將不影響共享狀態且耗時較長的代碼分離出去。

當執行時間較長的操作或可能無法快速完成的操作(I/O操作)時,一定不要加鎖。

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