Java併發——原子變量和原子操做

      不少狀況下咱們只是須要一個簡單的、高效的、線程安全的遞增遞減方案。注意,這裏有三個條件:簡單,意味着程序員儘量少的操做底層或者實現起來要比較容易;高效意味着耗用資源要少,程序處理速度要快;線程安全也很是重要,這個在多線程下能保證數據的正確性。這三個條件看起來比較簡單,可是實現起來卻難以使人滿意。html

      一般狀況下,在Java裏面,++i或者--i不是線程安全的,這裏面有三個獨立的操做:得到變量當前值,爲該值+1/-1,而後寫回新的值。在沒有額外資源能夠利用的狀況下,只能使用加鎖才能保證讀-改-寫這三個操做是「原子性」的。java

      Java 5新增了AtomicInteger類,該類包含方法getAndIncrement()以及getAndDecrement(),這兩個方法實現了原子加以及原子減操做,可是比較不一樣的是這兩個操做沒有使用任何加鎖機制,屬於無鎖操做。      程序員

      在JDK 5以前Java語言是靠synchronized關鍵字保證同步的,這會致使有鎖(後面的章節還會談到鎖)。算法

      鎖機制存在如下問題:安全

      (1)在多線程競爭下,加鎖、釋放鎖會致使比較多的上下文切換和調度延時,引發性能問題。數據結構

      (2)一個線程持有鎖會致使其它全部須要此鎖的線程掛起。多線程

      (3)若是一個優先級高的線程等待一個優先級低的線程釋放鎖會致使優先級倒置,引發性能風險。性能

      volatile是不錯的機制,可是volatile不能保證原子性。所以對於同步最終仍是要回到鎖機制上來。this

      獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,會致使其它全部須要鎖的線程掛起,等待持有鎖的線程釋放鎖。而另外一個更加有效的鎖就是樂觀鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試,直到成功爲止。spa

CAS 操做

      上面的樂觀鎖用到的機制就是CAS,Compare and Swap。

      CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。

非阻塞算法 (nonblocking algorithms)

一個線程的失敗或者掛起不該該影響其餘線程的失敗或掛起的算法。

      現代的CPU提供了特殊的指令,能夠自動更新共享數據,並且可以檢測到其餘線程的干擾,而 compareAndSet() 就用這些代替了鎖定。

      拿出AtomicInteger來研究在沒有鎖的狀況下是如何作到數據正確性的。

private volatile int value;

    首先毫無疑問,在沒有鎖的機制下須要藉助volatile原語,保證線程間的數據是可見的(共享的),這樣獲取變量值的時候才能直接讀取。

public final int get() {
        return value;
    }

      而後來看看++i是怎麼作到的。

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

      在這裏採用了CAS操做,每次從內存中讀取數據而後將此數據和+1後的結果進行CAS操做,若是成功就返回結果,不然重試直到成功爲止。

      而compareAndSet利用JNI來完成CPU指令的操做。

public final boolean compareAndSet(int expect, int update) {   
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

      總體的過程就是這樣子的,利用CPU的CAS指令,同時藉助JNI來完成Java的非阻塞算法。其它原子操做都是利用相似的特性完成的。

      而整個J.U.C都是創建在CAS之上的,所以對於synchronized阻塞算法,J.U.C在性能上有了很大的提高。參考資料的文章中介紹了若是利用CAS構建非阻塞計數器、隊列等數據結構。

      CAS看起來很爽,可是會致使「ABA問題」。

      CAS算法實現一個重要前提須要取出內存中某時刻的數據,而在下時刻比較並替換,可是在這個時間差內任何變化均可能發生。

      好比說一個線程one從內存位置V中取出A,這時候另外一個線程two也從內存中取出A,而且two進行了一些操做變成了B,而後two又將V位置的數據變成A,這時候線程one進行CAS操做發現內存中仍然是A,而後one操做成功。儘管線程one的CAS操做成功,可是不表明這個過程就是沒有問題的。若是鏈表的頭在變化了兩次後恢復了原值,可是不表明鏈表就沒有變化。要解決"ABA問題",咱們須要增長一個版本號,在更新變量值的時候不該該只更新一個變量值,而應該更新兩個值,分別是變量值和版本號,AtomicStampedReference支持在兩個變量上進行原子的條件更新,可使用該類進行更新操做。

參考資料:

(1)非阻塞算法簡介

(2)流行的原子

 

轉自:http://www.blogjava.net/xylz/archive/2010/07/04/325206.html

相關文章
相關標籤/搜索