【連載】第2章-2.2原子性 原子性 競態條件 知識點

格言:在程序猿界混出點名堂!

《JAVA併發編程實戰》解讀
【連載】第2章-2.2原子性

回顧:上一節主要介紹什麼是線程安全性,這一節講的是大家經常聽到的原子性。
比如Atomic包、事務的特徵ACID,其中的A指的就是原子性。

原子性

先看一下下面線程不安全的例子:

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

第1章中我們有提到過i++,是三步完成的非原子操作,如果這個servlet是多線程同時訪問,可能丟失計數。

競態條件

根據上面的例子,引申這個定義:
競態條件:由於不恰當的執行時序,而出現不正確的結果。

第1章舉過這個例子:A線程獲取i=0,B線程獲取i=0,A線程修改i=1,B線程修改i=1,A線程寫入i=1,B線程寫入i=1,導致最終結果出錯,這個就是競態條件。

  • 延遲初始化的競態條件

最常見的競態條件的類型就是“先檢查後執行

延遲初始化導致競態條件的例子,非線程安全的單例

@NotThreadSafe
public class LazyInitRace{
  private ExpensiveObject instance = null;
  public ExpensiveObject getInstance(){
    if(instance == null)
        instance = new ExpensiveObject();

    return instance; 
 }
}

上面的例子包含了一個競態條件,這裏例子存在的問題我就不解釋,大家天天在使用的單例設計模式。

  • 複合操作
    爲了避免競態條件,我們需要將複合操作原子化,即將“先檢查後修改”或者“獲取-修改-寫入”這樣的操作封裝具有原子性原子性,看一下開篇提到的非線程安全的示例如何進行復核操作原子性:
  @ThreadSafe
 public class UnsafeCountingFactorizer implements Servlet{
   // 線程共享
   private 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);
    }
 }

AtomicLong的incrementAndGet這個操作爲原子操作,採用volatile+CAS的方式來更新保證共享變量的原子性。

知識點

  1. 理解原子性競態條件複合操作

喜歡連載可關注簡書或者微信公衆號
簡書專題:Java併發編程實戰-可愛豬豬解讀
https://www.jianshu.com/c/ac717321a386
微信公衆號:逗哥聊IT

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章