1. 程式人生 > >Java併發:CAS、ABA問題、ABA問題解決方案

Java併發:CAS、ABA問題、ABA問題解決方案

【1】鎖

1、加鎖的機制

參見:java執行緒安全和鎖機制詳解

網址:http://smallbug-vip.iteye.com/blog/2275743

2、鎖的機制有如下問題

(1)在多執行緒環境下,加鎖、釋放鎖會導致比較多的上下文切換和排程延時,從而引起效能問題。

(2)一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起。

(3)如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖會導致優先順序倒置,引起效能風險。

3、悲觀鎖和樂觀鎖

(1)獨佔鎖:是一種悲觀鎖,synchronized就是一種獨佔鎖,會導致其它所有需要鎖的執行緒掛起,等待持有鎖的執行緒釋放鎖。 樂觀鎖:每次不加鎖,假設沒有衝突去完成某項操作,如果因為衝突失敗就重試,直到成功為止。

(2)volatile是不錯的機制,但是volatile不能保證原子性。因此,對於同步問題最終還是要回到鎖機制上來。

【優質文章】深入理解Java記憶體模型(四)——volatile:http://www.open-open.com/lib/view/open1459412319988.html 

【2】CAS方法:CompareAndSwap

1、樂觀鎖的使用的機制就是CAS。

在CAS方法中,CAS有三個運算元,記憶體值V,舊的預期值E,要修改的新值U。當且僅當預期值E和記憶體值V相等時,將記憶體值V修改為U,否則什麼都不做。

2、非阻塞演算法(nonblocking algorithms):一個執行緒的失敗或者掛起不應該影響其他執行緒的失敗或掛起的演算法。

(1)非阻塞演算法簡介:https://www.ibm.com/developerworks/cn/java/j-jtp04186/

(2)非阻塞演算法通常叫作樂觀演算法,因為它們繼續操作的假設是不會有干擾。如果發現干擾,就會回退並重試。

3、CAS方法

(1)CompareAndSwap()就使用了非阻塞演算法來代替鎖定。

(2)舉例:AtomicInteger來研究在沒有鎖的情況下是如何做到資料正確性的。

在沒有鎖機制的情況下,要保證執行緒間的資料是可見的,就會常常用到volatile原語了。

private volatile int value;

可使用如下方法讀取記憶體變數值value: 

public final int getValue(){
    return value;
}

遞增計數器是如何實現的:

public final int incrementAndGet(){
    for(;;){
        int current = getValue();
        int next = value + 1;
        if(compareAndSet(current,next)){
            return next;
        }
    }
}

該方法採用了CAS操作,每次從記憶體中讀取資料然後將此資料和+1後的結果進行CAS操作,如果成功就返回結果,否則重試直到成功為止。

而compareAndSet操作利用了Java本地介面(JNI,Java Native Interface)完成CPU指令的操作:

public final boolean compareAndSet(int expect, int update){
    return unsafe.compareAndSwapInt(this,valueOffset,expect,unsafe);
}
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
      try {
        valueOffset = unsafe.objectFieldOffset(*.class.getDeclaredField("value"));
      } catch (Exception ex) { 
      throw new Error(ex); }
}

【3】“ABA”問題

1、可以發現,CAS實現的過程是先取出記憶體中某時刻的資料,在下一時刻比較並替換,那麼在這個時間差會導致資料的變化,此時就會導致出現“ABA”問題。

2、什麼是”ABA”問題?

比如說一個執行緒one從記憶體位置V中取出A,這時候另一個執行緒two也從記憶體中取出A,並且two進行了一些操作變成了B,然後two又將V位置的資料變成A,這時候執行緒one進行CAS操作發現記憶體中仍然是A,然後one操作成功。 儘管執行緒one的CAS操作成功,但是不代表這個過程就是沒有問題的。

ABA問題是一種異常現象:如果在演算法中的節點可以被迴圈使用,那麼在使用“比較並交換”指令時就可能出現這個問題(如果在沒有垃圾回收機制的環境 中)。在CAS操作中將判斷“V的值是否仍然為A?”,並且如果是的話就繼續執行更新操作。在大多數情況下,這種判斷是足夠的。然而,有時候還需要知道 “自從上次看到V的值為A以來,這個值是否發生了變化?”在某些演算法中,如果V值首先由A程式設計B,在由B程式設計A,那麼仍然被認為發生了變化,並需要重新執 行演算法中的某些步驟。  

如果在演算法中採用自己的方式來管理節點物件的記憶體,那麼可能出現ABA問題。在這種情況下,即使連結串列的頭結點仍然只想之前觀察到的節點,那麼也不足 以說明連結串列的內容沒有發生變化。如果通過垃圾回收器來管理連結串列節點仍然無法避免ABA問題,那麼還有一個相對簡單的解決方法:不是隻是更新某個引用的值, 而是更新兩個值,包含一個引用和一個版本號。即使這個值由A變成B,然後又變為A,版本號也將是不同的。AtomicStampedReference以 及AtomicMarkableReference支援在兩個變數上執行原子的條件更新。AtomicStampedReference將更新一個“物件 —-引用”二元組,通過在引用上加上“版本號”,從而避免ABA問題。類似地,AtomicMarkableReference將更新一個“物件引用—- 布林值”二元組,在某些演算法中將通過這種二元組使節點儲存在連結串列中同時又將其標記為“已刪除節點”。 

【4】用AtomicStampedReference/AtomicMarkableReference解決ABA問題

1、原子操作:http://www.blogjava.net/xylz/archive/2010/07/02/325079.html

2、用AtomicStampedReference解決ABA問題:http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html

3、高併發Java(4):無鎖: http://www.importnew.com/21282.html

4、AtomicStampedReference、AtomicMarkableReference原始碼分析,解決cas ABA問題: https://blog.csdn.net/zqz_zqz/article/details/68062568

5、關於AtomicStampedReference使用的坑: https://blog.csdn.net/xybz1993/article/details/79992120

6、JAVA中的CAS: https://blog.csdn.net/mmoren/article/details/79185862

7、看看別人的Java面試 你是否又有學習的動力了?: http://www.jizhuomi.com/software/707.html