併發案例(一)


 
1.非線程安全 
    懶加載 ,是非線程安全的。
    當作一個線程剛非空判斷時,另外一個線程也進入非空判斷,則致使兩個線程都建立了一個ExpensiveObject對象。違背預期。
 
@NotThreadSafe
public class LazyInitRace {
   private ExpensiveObject instance = null;
 
   public ExpensiveObject getInstance() {
        if (instance == null){
            instance = new ExpensiveObject();
        }
        return instance;
    } 
}
 

 
2.線程安全
    當沒有共享的狀態變量時,該類是線程安全。
 
@ThreadSafe
public class StatelessFactorizer implements Servlet {
 
    public void service(ServletRequest req, ServletResponse resp) {
       BigInteger i = extractFromRequest(req);
       BigInteger[] factors = factor(i);
       encodeIntoResponse(resp, factors);
    }
 
}
 

 
3.非線程安全
    因爲 ++ 操做符是非原子性操做的(要通過 讀取原數字 ->  增長值 ->  返回新值賦給count ).所以會有併發風險,須要進一步封裝處理
 
@NotThreadSafe
public class UnsafeCountingFactorizer 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);
   }
}
 
 

 
4.線程安全
    AtomicLong 是java提供的線程安全對象。
    本質是把原始類型數據的非原子性操做封裝成原子性操做,以解決併發安全問題。
    Atomic* 原子操做實現原理: 
        for循環中調用unsafe.compareAndSwapint,判斷當前值是否等於current。
        若是不等於則繼續循環判斷;若是等於則賦值並返回增加後的值。
        而unsafe是用final修飾的。
        
 
    注意,在只有一個對象狀態變量時 ,是有效的。
 
@ThreadSafe
public class CountingFactorizer implements Servlet {
 
   private final AtomicLong count = new AtomicLong(0);
 
   public long getCount() { return count.get(); }
 
   public void service(ServletRequest req, ServletResponse resp) {
 
       BigInteger i = extractFromRequest(req);
       BigInteger[] factors = factor(i);
       count.incrementAndGet();
       encodeIntoResponse(resp, factors);
 
   }
}
 

 
5.線程安全,性能較差
    本例將 service方法 進行了synchornized 處理 ,所以同一時間只有一個線程能夠持有該鎖。
    當該線程退出該方法,會釋放鎖 ,其它線程才能夠得以進入。
    這樣雖然解決了 線程安全問題 ,但若是service處理的操做比較複雜,致使一個線程長期持有該鎖,則性能問題暴露明顯。
 
@ThreadSafe
public class SynchronizedFactorizer 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);
       }
   }
 
}
 

 
6.線程不安全
   判斷 lastNumber 的值 和 輸入值i ,是否相等後 ,若是有其餘線程 在這一瞬間作了 lastNumber.set()操做 ,則數據更新丟失;反之亦然。
   注意本例,雖然使用了 final 來修飾變量 ,又使用了AtomicReference(java提供的線程安全對象),可是因爲是兩個變量,依然不是原子性操做,須要進一步封裝
 
@NotThreadSafe
public class UnsafeCachingFactorizer 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);
       }
   }
}
 

 
7.線程安全,併發接受量大(性能較好)
    解決case 6 的問題,可使用 synchronized 修飾service方法,可是會有性能問題/併發接收量低 的問題,相似case 5.
    所以採用 ,將 檢測操做 和 賦值操做 分離開 (同時也是將長持有 拆分爲短持有),而且都用內置鎖(synchronized),把代碼塊鎖住。能夠提高性能。
 
    注意: 
        一共四個狀態變量。
        hits,cacheHits 相關操做不是在synchronized方法中,就是在synchronized塊中。
        lastNumber,lastFactors 在synchronized塊中。
        i.equals(lastNumber) 和 lastNumber,lastFactors 操做 分離在兩個鎖塊中 —- 檢測與操做分離,保證線程安全。
 
@ThreadSafe
public class CachedFactorizer 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);
   }
}
相關文章
相關標籤/搜索