格言:在程序猿界混出點名堂!
《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的方式來更新保證共享變量的原子性。
知識點
- 理解
原子性
、競態條件
、複合操作
。
喜歡連載可關注
簡書
或者微信公衆號
:
簡書專題:Java併發編程實戰-可愛豬豬解讀
https://www.jianshu.com/c/ac717321a386
微信公衆號:逗哥聊IT
。