本書提供了各類實用的設計規則,用於幫助開發人員建立安全的和高性能的併發類。
當多個線程訪問某個類時,這個類始終都能表現出正確的行爲,那麼就稱這個類是線程安全的。
@NotThreadSafe public class UnsafeCountingFactorizer extends GenericServlet implements Servlet { private long count = 0; public long getCount() { return count; } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); ++count;//破壞了線程的安全性 ,非原子性操做 encodeIntoResponse(resp, factors); } void encodeIntoResponse(ServletResponse res, BigInteger[] factors) { } BigInteger extractFromRequest(ServletRequest req) { return new BigInteger("7"); } BigInteger[] factor(BigInteger i) { // Doesn't really factor return new BigInteger[] { i }; } }
在併發編程中,因爲不恰當的執行時序而出現的不正確結果是一種很是重要的狀況,被稱之爲競態條件。 1)當某個計算結果的正確性取決於多線程的交替執行時序是,那麼就會出現競態條件。換句話說,那就是正確的結果取決於運氣。 2)競態條件的本質——基於可能失效的觀察結果來作出判斷或者執行某個計算。 這類競態條件被稱之爲「先檢查後執行」。 下面是一種常見狀況,延遲初始化。 @NotThreadSafe public class LazyInitRace { private ExpensiveObject instance = null; public ExpensiveObject getInstance() { if (instance == null) instance = new ExpensiveObject(); return instance; } } class ExpensiveObject { }
UnsafeCountingFactorizer 和 LazyInitRace 都包含一組須要以原子方式執行(或者說不可分割)的操做。
相似AtomicLong的AtomicRreference來管理因數分解的數值及分解結果? // 這個方法不正確,儘管這些原子引用自己都是現成安全的,可是組合在一塊兒就不是線程安全的了。 //存在lastNumber和lastFactors沒有同時更新的狀況 @NotThreadSafe public class UnsafeCachingFactorizer extends GenericServlet implements Servlet { private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>(); private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>(); public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); if (i.equals(lastNumber.get())) encodeIntoResponse(resp, lastFactors.get()); else { BigInteger[] factors = factor(i); lastNumber.set(i); lastFactors.set(factors); encodeIntoResponse(resp, factors); } } void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) { } BigInteger extractFromRequest(ServletRequest req) { return new BigInteger("7"); } BigInteger[] factor(BigInteger i) { // Doesn't really factor return new BigInteger[]{i}; } } 要保持狀態一致性,就須要在單個原子操做中更新全部先關的狀態變量。
每一個java對象均可以用作一個實現同步的鎖,這些鎖被稱之爲內置鎖(Intrinsic lock)或監視器鎖(Monitor Lock)。線程在進入同步代碼塊(Synchronized Block)以前會自動得到鎖,而且在退出同步代碼塊時自動釋放鎖,而不管是經過正產的控制路徑退出,仍是經過從代碼塊中拋出異常退出。 得到鎖的位移方法就是進入由這個鎖保護的同步代碼快或者方法。
@ThreadSafe public class SynchronizedFactorizer extends GenericServlet implements Servlet { @GuardedBy("this") private BigInteger lastNumber; @GuardedBy("this") private BigInteger[] lastFactors; //同步方法 //併發性能太差,不推薦這麼作 public synchronized void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); if (i.equals(lastNumber)) encodeIntoResponse(resp, lastFactors); else { BigInteger[] factors = factor(i); lastNumber = i; lastFactors = factors; encodeIntoResponse(resp, factors); } } void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) { } BigInteger extractFromRequest(ServletRequest req) { return new BigInteger("7"); } BigInteger[] factor(BigInteger i) { // Doesn't really factor return new BigInteger[] { i }; } }
若是某個線程試圖得到一個已經由他本身持有的鎖,那麼這個請求就會成功。
「重入」意味着獲取鎖的操做的粒度是「線程」,而不是「調用」。java
注意兩點:編程
1 一般,在簡單性與性能之間存在着某種互相制約因素。當實現某個同步策略時,必定不要盲目地爲了性能而犧牲簡單性。 2 當執行時間較長的計算或者可能沒法快速完成的操做時(例如,網絡I/O操做或者控制檯I/O),必定不要持有鎖。