java併發編程實踐學習(15)原子變量與非阻塞同步機制

近來很多關於併發算法的研究機構都聚焦在非阻塞算法上,這種算法使用低層原子化的機器指令取代鎖,比如比較並交換

一、鎖的劣勢

當頻繁發生鎖的競爭時,調度與真正用於工作的開銷時間的比會變得很可觀。volatile變量與鎖相比是更清涼的同步機制,因爲他們不會引起上下文的切換和線程調度。
加鎖還有其他缺點。當一個線程正在等待鎖時,它不能做任何其他事情。如果一個線程在持有鎖的情況下發生了延遲,如果阻塞的線程是優先級很高的線程,持有鎖的線程是優先級較低的線程,那麼會造成優先級倒置的風險,即使高優先級佔先,他仍然需要等待鎖被釋放,這導致他的優先級會降至與優先級較低的線程相同的水平。

二、硬件對併發的支持

針對多處理器系統設計的處理器提供了特殊的指令,用來管理併發訪問的共享數據。早期處理器具有原子化的測試並設置,獲取並增加,以及交換指令,現代的處理器都具有一些形式的原子化的讀-改-寫指令,比如比較並交換和加載鏈接/存儲條件。操作系統和JVM使用這些指令來實現鎖和併發的數據結構。

1、比較並交換

包括IA32和、Sparc在內的大多數處理器使用的架構方案都實現了比較並交換指令(CAS)。CAS有三個操作數-內存位置V、舊的預期值A和新值B、當且僅當V符合舊預期值A時,CAS用新值B原子化的更新V的值;否則它什麼都不做。
當多個線程試圖調用CAS同時更新想的的變量時,其中一個會勝出,並更新變量的值,而其它都會失敗,失敗的線程不會被掛起;
模擬CAS操作

@ThreadSafe
public class SimulatedCAS{
    @GuardedBy("this")
    private int value;

    public synchronized int get(){
        return value;
    }

    public synchronized int compareAndSwap(int expectedValue,int new Value){
        int = oldValue = value;
        if(oldValue == value){
            value = new Value;
        }
        return oldValue;
    }

    public synchronized boolean compareAndSet(int expectedValue, int newValue){
        return (expectedValue == compareAndSwap(expectedValue,newValue));
    }
}

2、JVM對CAS的支持

在Java5.0中引入了CAS的底層支持,將int、long和對象的引用暴露給CAS操作,並且JVM把它們編譯爲底層硬件提供的最有效的方法。在支持CVS的平臺上,運行時把它們編排成恰當的機器指令,在CAS不可用的情況下,JVM纔會使用自旋鎖,這些底層的JVM支持用於那些具有原子化變量的類(java.util.concurrent.atomic中的AtomicXXX),而且這些原子變量類還用於直接或間接的實現java.util.concuttent中的大部分實現。

三、原子變量類

原子變量類,提供了廣義的volatile變量,以及支持原子的、條件的讀-寫改操作
原子變量類共有12個,分成四組:計量器、域更新器、數組以及複合變量。最常用的原子變量是計量器:AtomicInteger、AtomicLong、AtomicBoolean以及AtomicReference。它們都支持CAS,AtomicInteger和AtomicLong還支持算數運算,對於short和byte把它們的值強制轉化爲int;對於浮點數,使用floatToIntBits或doubleToLongBits。
原子化數組類(只有Integer、Long和Reference版本可用)它的元素可以被原子化的更新。原子數組類爲數組元素提供了volatile的訪問語義,這是普通數組元素沒有的特性。

4.非阻塞算法

一個線程的失敗或掛起不影響其他線程的失敗或掛起,這樣的算法被稱爲爲阻塞算法;如果算法的每一步中都有一些線程能夠繼續執行,這樣的算法被稱爲鎖自由算法。在線程間使用CAS進行協調,這樣的算法如果能構建正確的話,它既是非阻塞的,又是鎖自由的。

1、非阻塞棧

在鏈式容器類中,有時你可以 不必在進行轉化了而把它變爲對單獨鏈接的修改,你可以使用一個AtomicReference來表達每一個必須被原子的鏈接。
棧是最簡單的鏈式數據結構:每個元素僅僅引用唯一的元素,並且每個元素值被一個元素引用。
使用Treiber算法的非阻塞棧

public class ConcurrentStack<E> {
    AtomicReference<Node<E>> head = new AtomicReference<Node<E>>();
    public void push(E item) {
        Node<E> newHead = new Node<E>(item);
        Node<E> oldHead;
        do {
            oldHead = head.get();
            newHead.next = oldHead;
        } while (!head.compareAndSet(oldHead, newHead));
    }
    public E pop() {
        Node<E> oldHead;
        Node<E> newHead;
        do {
            oldHead = head.get();
            if (oldHead == null) 
                return null;
            newHead = oldHead.next;
        } while (!head.compareAndSet(oldHead,newHead));
        return oldHead.item;
    }
    static class Node<E> {
        final E item;
        Node<E> next;
        public Node(E item) { this.item = item; }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章