非阻塞算法-簡單的計數器

1.爲何要用非阻塞算法?
咱們知道爲了不併發環境下操做共享變量的問題,能夠採用同步(synchronize)和鎖(Lock)的方式作到線程安全,可是JVM處理鎖競爭時對於競爭失敗的線程採用的是掛起稍後調度的策略,這樣會帶來額外的線程上下文切換成本。同時和CAS(Compare And Set)這種非阻塞算法相比,CAS是在底層硬件(CPU)層面實現,只須要鎖定獨立的內存位置,更細的同步粒度使得CAS失敗的線程能夠當即重試而不用掛起。總的來講,大多數場景下非阻塞算法和同步鎖相比能帶來更好的性能,最小化串行代碼的粒度是並行性能的關鍵。java

2.非阻塞算法的弊端
CAS算法是先compare再set,若是compare結果爲false的話就不會執行set直接返回false,也就是說非阻塞算法可能會失敗,所以非阻塞算法每每須要放在循環裏不斷重試直到成功,因此在鎖競爭很是嚴重時非阻塞算法的性能可能會嚴重降低,甚至不如阻塞算法。算法

3.最簡單的CAS用法
CAS的全稱是Compare and Set,是現代主流CPU都支持的一個CPU原子語義操做,簡單來講就是先跟某個值對比,若是等於這個值就設置成新的值,若是不等於這個值就放棄操做同時返回失敗。這裏咱們看一下最經常使用的計數器,首先咱們看一個單線程的計數器:安全

public class SingleThreadCounter implements Counter {

    @Override
    public void increment() {
        this.counter++;
    }

    @Override
    public int getCounter() {
        return counter;
    }

    private int counter = 0;
}

這個計數器在單線程下徹底沒有問題,可是一旦有多個線程併發執行increment,就會出現問題,由於counter++ 是先取值,再賦值,若是A線程取出1,B線程也取出1,A設置counter爲2,B後設置counter也爲2,最終B把A的結果覆蓋了。數據結構

接下來咱們看同步的計數器多線程

public class SynchronizedCounter implements Counter {

    private volatile int counter = 0;

    public synchronized void increment() {
        counter = counter + 1;
    }


    @Override
    public int getCounter() {
        return counter;
    }
}

這個計數器解決了前面一個多線程併發increment的問題,由於increment方法是同步的,不會有兩個線程同時執行increment操做,實際上increment操做是串行的。這種方式保證了線程安全,可是犧牲了併發度。併發

最後咱們看無鎖算法的計數器ide

public class CasCounter implements Counter {

    private AtomicInteger counter = new AtomicInteger(0);

    @Override
    public void increment() {
        for (; ; ) {
            int cur = counter.get();
            int next = cur + 1;
            if (counter.compareAndSet(cur, next)) {
                return;
            }
        }
    }

    @Override
    public int getCounter() {
        return counter.get();
    }
}

咱們看這個類裏的increment方法,其實這個方法和AtomicInteger裏的incrementAndGet方法是同樣的,就是在每一個循環裏作一次CAS操做,先取出當前拿到的值cur,而後CAS(cur,cur+1),若是返回成功的話說明更新成功了,那就直接return;若是失敗的話說明其餘線程已經改變了counter的值,就繼續循環執行前面的操做直到成功爲止。這裏須要注意的是counter.get方法返回的底層變量是一個volatile的值,能夠保證內存的可見性,所以不會出現讀到髒值的問題。性能

4.總結
CAS算法和同步鎖相比,把串行的粒度從方法級別下降到CPU的CAS原子操做,提升了併發度,所以提升了系統的容量,可是CAS在失敗時是須要重試的,所以若是併發度很高,競爭十分嚴重的狀況下,由於須要大量重試操做,CAS的效率可能甚至不如同步方法。
下一節咱們繼續講如何把CAS算法用在一些更復雜一些的數據結構上。this

相關文章
相關標籤/搜索