-
對象的狀態(tài)
(1) 對象的狀態(tài)是指 類的實例或靜態(tài)變量
(2) 也包括其他依賴對象的域
例如某個HashMap的狀態(tài)不僅存儲在HashMap對象本身,還存儲在許多Map.Entry對象中
-
Java的同步機制
(1) synchronized
(2) volatile
(3) 顯示鎖
(4) 原子變量: package java.util.concurrent.atomic
-
將線程不安全的類修改為線程安全的類的方法
(1) 不在線程之間共享狀態(tài)變量
(2) 將狀態(tài)變量修改為不可變變量
(3) 在訪問狀態(tài)變量時使用同步
程序狀態(tài)的封裝性越好, 越容易實現(xiàn)程序的線程安全性
-
線程安全性
(1) 定義
當多個線程訪問某個類時,不論運行時環(huán)境采用何種調(diào)度方式, 或者線程如何調(diào)度執(zhí)行, 在主調(diào)代碼中不需要任何額外的同步, 這個類總能表現(xiàn)出正確的行為
(2) 無狀態(tài)的類一定是線程安全的
即,類中沒有任何靜態(tài)變量和實例變量,類只提供方法,所有變量都是局部變量。
沒有狀態(tài)變量,自然也就沒有線程同步的問題
-
競態(tài)條件
(1) 定義
由于執(zhí)行時序的不同,造成不正確的結(jié)果稱為競態(tài)條件
(2) 常見的競態(tài)條件情形
1° 先檢查后執(zhí)行
示例
if (!flag) { .... }2° 讀取-修改-寫入
示例
count += 1(因為count+=1這一條語句事實上包含了讀取-修改-寫入三個步驟,不是原子操作)
(3) 當在無狀態(tài)的類中添加一個狀態(tài)變量時,如果該狀態(tài)完全由線程安全的對象來管理,那么這個類一定是線程安全的
實際情況中,應(yīng)盡可能使用線程安全的類作為狀態(tài)變量,例如java.util.concurrent.atomic中的類
示例
線程不安全
@NotThreadSafe public class UnsafeCountingFactorizer extends GenericServlet implements Servlet { private long count = 0; public long getCount() { return count; } public void service(ServletRequest req, ServletResponse resp) { ... ++count; ... } }線程安全
@ThreadSafe public class CountingFactorizer extends GenericServlet implements Servlet { private final AtomicLong count = new AtomicLong(0); public long getCount() { return count.get(); } public void service(ServletRequest req, ServletResponse resp) { ... count.incrementAndGet(); encodeIntoResponse(resp, factors); } }其中,count.get()和count.incrementAndGet();都是AtomicLong類提供的實例方法
(3) 狀態(tài)變量有多個時,對每個變量的更新都使用原子操作,結(jié)果未必是原子操作
正確的做法是:
當更新某一個狀態(tài)變量時, 如果各個狀態(tài)變量之間不是彼此獨立的,那么需要在同一個原子操作中, 對其他變量同時進行更新
-
內(nèi)置鎖與synchronized
(1) 每個Java對象都可以用作一個實現(xiàn)同步的鎖,稱為內(nèi)置鎖(Intrinsic Lock)或監(jiān)控器鎖(Monitor Lock),用作synchronized(...)
(2) 在synchronized塊中,無論程序正常運行結(jié)束,還是拋出異常,都會在退出同步代碼塊時自動釋放鎖
(3) 重入
如果一個線程試圖獲得一個已經(jīng)由它自己持有的鎖, 那么這個請求一定會成功, 不會引發(fā)死鎖
例如
1° 遞歸函數(shù) 如果加上了synchronized, 每次調(diào)用自己時不會被阻塞
2° __子類覆蓋了父類的synchronized方法, 且在方法中又顯示調(diào)用父類的方法時(super.xxx()),不會被阻塞
一種實現(xiàn)原理是, 為每個鎖關(guān)聯(lián)一個計數(shù)值和一個所有者線程,如果請求鎖時發(fā)現(xiàn)占有鎖的是所有者線程本身,那么只會增加計數(shù)值,不會阻塞
-
(1) 某個線程獲得對象的鎖以后, 只能阻止其他線程獲得同一個鎖, 不能阻止其他線程獲取其他的鎖
因此, 每個共享的和可變的變量都應(yīng)該只由一個鎖來保護
(2) 一種常見的加鎖約定
將所有的可變、共享的狀態(tài)變量都封裝在對象內(nèi)部, 并通過對象本身(this)的內(nèi)置鎖對所有訪問可變狀態(tài)的代碼路徑進行同步
-
同步代碼塊的大小
(1) 理論上, 可以將所有方法都盡可能加上synchronized,但是一來濫用會導(dǎo)致性能問題,二來每個方法都是原子操作,不代表整體就是原子操作
(2) 同步代碼塊的合理大小取決于三個因素
1° 安全性: 必須達到
2° 簡單性: 同步的部分設(shè)計不能過于復(fù)雜
3° 性能: 同步代碼塊范圍過大會導(dǎo)致性能問題
(3) 安全性必須達到, 簡單性和性能常常是矛盾的。
當實現(xiàn)某個同步策略時,一定不能盲目的為了性能而犧牲簡單性;
當執(zhí)行時間較長的計算和IO操作時,一定不要繼續(xù)持有鎖。
(4) 一個設(shè)計合理的示例
@ThreadSafe public class CachedFactorizer extends GenericServlet implements Servlet { @GuardedBy("this") private BigInteger lastNumber; @GuardedBy("this") private BigInteger[] lastFactors; @GuardedBy("this") private long hits; @GuardedBy("this") private long cacheHits; public synchronized long getHits() { return hits; } public synchronized double getCacheHitRatio() { return (double) cacheHits / (double) hits; } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = null; synchronized (this) { ++hits; if (i.equals(lastNumber)) { ++cacheHits; factors = lastFactors.clone(); } } if (factors == null) { factors = factor(i); synchronized (this) { lastNumber = i; lastFactors = factors.clone(); } } encodeIntoResponse(resp, factors); } private void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) { } private BigInteger extractFromRequest(ServletRequest req) { return new BigInteger("7"); } private BigInteger[] factor(BigInteger i) { return new BigInteger[]{i}; } }這個示例首先滿足了安全性; 其次將同步代碼塊的范圍盡可能縮小, 滿足了性能要求; 再次沒有將同步代碼塊拆的很零碎,滿足了簡單性要求。所以是一個設(shè)計良好的類。
對單個變量實現(xiàn)原子操作來說, 原子變量(AtomicXXX)很有效;但是由于多個原子操作的疊加未必是一個原子操作,所以當狀態(tài)變量很多時, 仍有考慮所有狀態(tài)變量的同步更新訪問問題,即便它們都使用了原子變量。
在這個示例中,對hits和cacheHits這兩個狀態(tài)變量的更新和訪問使用了synchronized機制,沒有必要引入原子變量,增大設(shè)計的復(fù)雜程度
-
總結(jié)
(1) 無狀態(tài)的類一定是線程安全的
(2) 當在無狀態(tài)的類中添加一個狀態(tài)變量時,如果該狀態(tài)完全由線程安全的對象來管理,那么這個類一定是線程安全的
例如只添加一個狀態(tài)變量時,添加一個原子變量類型AtomicXXX就很合適
(3) 當更新某一個狀態(tài)變量時, 如果各個狀態(tài)變量之間不是彼此獨立的,那么需要在同一個原子操作中, 對其他變量同時進行更新
一種常見的約定是: 將所有的可變、共享的狀態(tài)變量都封裝在對象內(nèi)部, 并通過對象本身(this)的內(nèi)置鎖對所有訪問可變狀態(tài)的代碼路徑進行同步
(4) 同步代碼塊的大小要合理,必須滿足安全性,且在簡單性和性能方面權(quán)衡合適