Java並發編程(三)概念介紹
在構建穩健的並發程序時,必須正確使用線程和鎖。但是這終歸只是一些機制。要編寫線程安全的代碼,其核心在於要對狀態訪問操作進行管理,特別是對共享的(Shared)和可變的(Mutable)狀態的訪問。
對象的狀態是指存儲在狀態變量(例如實例或靜態域)中的數據。
對象的狀態可能包括其他依賴對象的域。比如某個HashMap的狀態不僅是HashMap對象本身,還存儲在許多Map.Entry對象中。
"共享"意味著變量可以由多個線程同時訪問,而"可變"則意味著變量的值在其生命周期內可以變化。
線程安全性在於如何防止數據上發生不可控的並發訪問。
一個對象是否需要是線程安全的,取決於它是否被多個線程訪問。
要使對象是線程安全的,需要采用同步機制
協同多個線程對變量訪問的同步機制主要有:
1. 關鍵字synchronized
2. 關鍵字volatile
3. 顯式鎖(Explicit Lock)
4. 原子變量
協同多線程訪問一個可變的狀態變量的方法有:
1. 不在線程之間共享該狀態變量
2. 將狀態變量修改為不可變的變量
3. 在訪問狀態變量時使用同步
什麽是線程安全?
一個類在多線程環境下被訪問,這個類始終能表現出正確的行為,那麽就稱這個類是線程安全的。
在線程安全類中往往都封裝了必要的同步機制,因此客戶端無須進一步采取措施。
無狀態的對象一定是線程安全的。
無狀態可以極大降低類在實現線程安全性時的復雜性。只有當類在保存一些信息的時候,線程安全才會成為一個問題。
原子性
當一個操作被CPU無可分割地執行時就是原子操作。
典型的非原子性操作就是a++,但是實際上這是一個"讀取—修改—寫入"的操作序列,並且結果狀態依賴於之前的狀態。
在並發編程中,由於不恰當的執行順序而出現不正確的結果是一種非常重要的情況,就是競態條件(Race Condition)。
競態條件
當某個計算的正確性取決於多個線程的交替執行時序時,那麽就會發生競態條件。最常見的競態條件就是"先檢查後執行(Check-Then-Act)"操作,即通過一個可能失效的觀測結果來決定下一步的動作。比如延遲初始化的單例模式。
復合操作
要避免競態條件的問題,就必須在某個線程修改該變量時,通過某種方式防止其他線程使用這個變量,從而確保其他線程只能在修改操作完成或者之後讀取和修改狀態,而不是在修改狀態的過程中。為了確保安全性,"先檢查後執行"(例如延遲初始化)和"讀取—修改—寫入"(如遞增運算)等必須是原子的。我們把"先檢查後執行"(例如延遲初始化)和"讀取—修改—寫入"等操作統稱為復合操作:包含了一組必須以原子方式執行的操作以確保線程安全性。
復合操作
加鎖機制
內置鎖
同步代碼塊
重入
Java並發編程(三)概念介紹