摘要:第二章線程安全性線程安全性的理解定義某個類的行為與其規范完全一致原子性競態條件理解當操作的正確的結果取決于多個線程的交替執行時序,就會發生競態條件。
第二章 線程安全性 2.1 線程安全性的理解
定義:某個類的行為與其規范完全一致
2.2 原子性 2.2.1 競態條件理解:當操作的正確的結果取決于多個線程的交替執行時序,就會發生競態條件。常見的競態條件類型是
”先檢查后執行“,首先觀察到某個條件為真再去采取下一步的動作(然而在這兩個操作之間其實觀察結果可能失效)
一個很常見的例子就是延遲初始化。當一個線程根據觀察結果為空進行初始化時,一個線程可能已經建立了初始化。
@NotThreadSafe public class LazyInitRace { private SampleClass sample; public SampleClass getInstance() { if (instance == null) { instance = new SampleClass(); } return instance; } }2.2.3 復合操作
原子操作:對于訪問同一個狀態的所有操作(包括該操作本身)來說,這個操作是以一個原子方式來執行的操作,也即不可分割的操作。如這些操作:先檢查后執行,讀取-修改-寫入等復合操作,包含了一組必須以原子方式執行的操作以確保其線程安全性。
2.3 加鎖機制線程安全性的定義中要求,多個線程之間無論采取何種執行時序或交替方式,都要保證不變性條件不被破壞。當在不變性條件中涉及多個策略時,各個變量之間不是彼此獨立的話,某個變量的值就會對其他變量的值產生約束。要保持狀態的一致性,就需要在單個原子操作中更新所有相關的狀態變量。
2.3.1 內置鎖Java提供的一種內置鎖機制:同步代碼塊。同步代碼塊包括兩個部分:有個作為鎖的對象引用,一個作為由這個鎖保護的代碼塊,常見的有synchronized關鍵字:
synchronized (lock) { // TODO }
附:
每個Java對象都可以用作一個實現同步的鎖,這些鎖被稱為內置鎖或監視器鎖。
當某個線程請求一個由其他線程持有的鎖時,發出請求的線程就會被阻塞。而如果這個線程試圖獲取已經由它自己持有的鎖,則這個請求就會成功。否則,就會導致死鎖的發生。簡單示例如下:
public class Parent { public synchronized void parentMethod { ... } } public class Child extends Parent { public synchronized void childMethod { ... } }2.4 用鎖來保護狀態
首先了解一個概念,共享狀態:這里指各個線程共享的有狀態對象等。通過鎖來構造一些協議,以實現對共享狀態的獨占訪問,只要始終遵循這些協議,就能確保狀態的一致性。
當復合操作訪問共享狀態時,如讀取-修改-寫入,必須是原子操作以避免產生競態條件。
還需要理解的幾個點:1、僅僅將復合操作封裝到一個同步代碼塊中是不夠的。如果用同步來協調對某個變量的訪問,那么在訪問這個變量的所有位置上都需要同步。2、當使用鎖來協調對某個變量的訪問時,在訪問變量的所有位置上都要使用同一個鎖。3、對于可能被多個線程同時訪問的可變狀態變量,在訪問它時都需要持有同一個鎖,在這種情況下,我們稱狀態變量是由這個鎖來保護的。4、每個共享的和可變的變量都應該只有一個鎖來保護,從而使維護人員知道是哪一個鎖。
一種加鎖的約定是:使所有的可變狀態都封裝在對象內部,并通過對象的內置鎖對所有訪問可變狀態的代碼路徑·進行同步,使得該對象上不會發生同步,如Vector類等。
當類的不變性涉及到多個狀態變量時,那么還有另外一個需求:在不變性條件中的每個變量都必須由同一個鎖來保護。
不良并發程序:可同時調用的數量,不僅受到可用處理資源的限制,還受到應用程序本身的限制。一種改良做法是(書中給出的),要確保同步代碼塊不要過小,且不要將本應是原子的操作拆分到多個同步代碼中。應盡量將不影響共享狀態且執行時間較長的操作從同步代碼中分離出去,從而在這些操作的執行過程中,其他線程可以訪問共享狀態。
public class CachedFactorizer implements Servlet { private BigInteger lastNumber; private BigInteger[] lastFactors; private long hits; private long cacheHits; public synchronized long getHits() { return hits; } public synchronized double getCachedHits() { return (double)cacheHits / (double)hits; } public void service(ServletRequest req, ServletReponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = null; synchronized(this) { ++hits; if (i.equals(lastNumber)) { ++cacheHits; factors = lastFactors.clone(); } } if (factors == null) { factors = getFactor(i); synchronized(this) { lastNumber = i; lastFactors = factors.clone(); } } encodeIntoResponse(resp, factors); } }
要判斷同步代碼的和李大霄,需要在各個設計需求之間進行權衡,包括安全性、簡單性和性能。如果持有鎖的時間過長,那么會帶來活躍性或性能問題:當執行時間較長的計算或者可能無法快速完成的操作時(例如,網絡I/O或控制態I/O,一定不要持有鎖。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68321.html
摘要:在之前的文章中學習了關鍵字,可以保證變量在線程間的可見性,但他不能真正的保證線程安全。線程執行到指令時,將會嘗試獲取對象所對應的的所有權,即嘗試獲得對象的鎖。從可見性上來說,線程通過持有鎖的方式獲取變量的最新值。 在之前的文章中學習了volatile關鍵字,volatile可以保證變量在線程間的可見性,但他不能真正的保證線程安全。 /** * @author cenkailun *...
摘要:線程的基本狀態線程的基本操作與內存模型線程組守護線程線程優先級線程安全與隱蔽錯誤線程的基本狀態線程的生命周期線程的基本操作新建線程終止線程立即終止線程所有活動方法在結束線程時會直接終止線程并立即釋放這個線程所持有的鎖可能引起數據不一致強烈建 1.線程的基本狀態 2.線程的基本操作 3. volatile與java內存模型 4.線程組 5.守護線程(Daemon) ...
摘要:源碼和多線程安全問題分析在分析線程安全問題之前,我們線對此類的源碼進行分析,找出可能出現線程安全問題的地方,然后代碼進行驗證和分析。即當多線程調用方法的時候會出現元素覆蓋的問題。 1.ArrayList源碼和多線程安全問題分析 在分析ArrayList線程安全問題之前,我們線對此類的源碼進行分析,找出可能出現線程安全問題的地方,然后代碼進行驗證和分析。 1.1 數據結構 ArrayLi...
摘要:如果需要防范這種攻擊,請修改構造函數,使其在被要求創建第二個實例時拋出異常。單例模式與單一職責原則有沖突。源碼地址參考文獻設計模式之禪 定義 單例模式是一個比較簡單的模式,其定義如下: 保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。 或者 Ensure a class has only one instance, and provide a global point of ac...
閱讀 3323·2021-11-25 09:43
閱讀 3008·2021-10-15 09:43
閱讀 1965·2021-09-08 09:36
閱讀 2918·2019-08-30 15:56
閱讀 742·2019-08-30 15:54
閱讀 2684·2019-08-30 15:54
閱讀 2973·2019-08-30 11:26
閱讀 1237·2019-08-29 17:27