Java併發程式設計(三)——原子操作
阿新 • • 發佈:2018-12-10
概念
不可被中斷的一個或一系列操作
術語定義
術語名稱 | 英文 | 解釋 |
---|---|---|
快取行 | cache line | 快取的最小操作單位 |
比較並交換 | Compare And Swap | CAS操作需要輸入兩個數值,一箇舊值(期望操作前的值)和一個新值,在操作期間先比較舊值有沒有變化,才交換成新值,發生了變化則不交換 |
CPU流水線 | CPU pipeline | CPU流水線的工作方式就像工廠裝配流水線,在CPU中有5-6個不用功能的電路單元組成一個指令處理流水線,然後將一條x86指令分成5-6步後在由這些電路單元分別執行,這樣就能實現一個CPU時鐘週期完成一條指令,因此提供CPU的運算速度 |
記憶體順序衝突 | Memory Order Violation | 記憶體順序衝突一般是由假共享引起的,假共享是指多個CPU同時修改用一個快取行的不同部分而引起其中一個CPU的操作無效,當出現記憶體順序衝突時,CPU必須清空流水線 |
處理器如何實現原子操作
- 使用匯流排鎖保證原子性
匯流排鎖就是使用處理器提供的一個LOCK#訊號,當一個處理器在總線上輸出此訊號時,其他處理器的請求將被阻塞,那麼處理器獨佔共享記憶體 - 使用快取鎖保證原子性
處理器不在總線上聲言LOCK#訊號,而是修改內部的記憶體地址,並允許它的快取一致性機制來保證操作的原子性,因為快取一致性會阻止同時修改由兩個以上處理器快取的記憶體區域資料,當其它處理器回寫已被鎖定的快取行的資料時,會使快取行無效。
Java如何實現原子性
Java中通過鎖和迴圈CAS的方式實現原子操作
迴圈CAS
JVM中的CAS操作正是利用了處理器提供的CMPXCHG指令實現的。自旋CAS實現的基本思路就是迴圈進行CAS操作直到成功為止。
public class Counter {
private AtomicInteger atomicI = new AtomicInteger(0);
public static void main (String[] args) {
final Counter cas = new Counter();
List<Thread> ts = new ArrayList<Thread>(600);
long start = System.currentTimeMillis();
for(int j = 0; j < 100 ; j++){
Thread t = new Thread(new Runable(){
public void run(){
for(int i = 0; i < 10000 ; i++ ){
cas.count();
cas.safecount();
}
}
});
ts.add(t);
}
for(Thread t : ts)
t.start();
//等待所有的執行緒執行完成
for(Thread t : ts){
try{
t.join();
}catch(Exception e){
e.printStackTrace();
}
}
System.out.println(cas.i);
System.out.println(cae.automicI.get());
System.out.println(System.currentTimeMillis()-start);
}
private void safeCount(){
for(;;){
int i = automicI.get();
boolean suc = atomicI.compareAndSet(i, ++i);
if(suc)
break;
}
}
/**非執行緒安全*/
private void count(){
i++;
}
}
JDK1.5之後,併發包提供一些類來進行原子操作,如AtomicBoolean,AutomicInteger,AtomicLog等
CAS原子操作的三大問題
ABA問題
CAS需要在操作值的時候,檢查有沒有變化,如果沒有發生變化則更新。如果一個值原來時A,改成B,又改成A。那麼使用CAS會發現它的值沒有變化。如果要解決ABA問題,可以在變數前加上版本號,1A,2B,3A來區分,如JDK提供AtomicStampedReference來解決,這個類的compareAndSet方法會先檢查當前引用是否等於預期引起,並檢查當前標誌是否等於預期標誌,如果都相等,則以原子的形式將該引用和標誌設定為新的值。迴圈時間長開銷大
CAS長時間不成功會給CPU帶來非常大的開銷只能保證一個共享變數的原子操作
對於一個共享變數我們可以通過CAS保證原子性,但是多個共享變數,要保證原子性就只能用鎖。
鎖機制實現原子操作
鎖機制保證只有獲得鎖的執行緒才能操作鎖定的記憶體區域,JVM內部有很多鎖機制,偏向鎖,輕量鎖和互斥鎖。除了偏向鎖,JVM實現鎖的方式都用了迴圈CAS(自旋),即當一個執行緒想進入同步塊的時候使用自旋的方式獲取鎖,當它退出同步塊時,採用自旋釋放鎖。