學習一

這本書的內容是什麼?

本書提供了各類實用的設計規則,用於幫助開發人員建立安全的和高性能的併發類。

什麼類是線程安全的?

當多個線程訪問某個類時,這個類始終都能表現出正確的行爲,那麼就稱這個類是線程安全的。

無狀態的sevlet是線程安全的, 當無狀態變爲有狀態時就是不安全的

@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 };
    }
}

競態條件(Race Condition)

在併發編程中,因爲不恰當的執行時序而出現的不正確結果是一種很是重要的狀況,被稱之爲競態條件。 
  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),必定不要持有鎖。
相關文章
相關標籤/搜索