Java實現原子操做

Java實現原子操做java

什麼是原子操做

http://www.infoq.com/cn/articles/atomic-operations-and-contention安全

計算機操做最重要的構成單位是原子操做。這裏的原子跟物理上說的原子沒有任何關係,而是起源於單詞atom,也就是希臘語「ἄτομος」(意爲不可見的)。原子操做是一種不可再細分的操做,或者在系統中其餘處理器看來是不可再分了。爲了說明爲何原子操做很重要,考慮兩個處理器以幾乎相同的方式增長一個計數器,翻譯成C語言就是counter++,此時會發生什麼:架構

指令週期 處理器一 處理器二
0 reg = load(&counter);  
1 reg = reg + 1; reg = load(&counter);
2 store(&counter, reg); reg = reg + 1;
3   store(&counter, reg);

在編譯好的代碼中,這樣一個操做分爲:讀操做、寄存器自加,最後是一個寫操做(這裏用相似C語言的僞代碼表示)。這三個步驟是獨立且按順序執行的(注意,對於x86來講,在更微觀的架構層次上這句話是正確的,可是在指令集架構的層次上,這三步看起來能夠用一條「讀-修改-寫(read-modify-write)」指令完成:add [memory], value)。而且由於這些操做被分紅多個指令週期來執行,因此在處理器一讀完counter(而且正開始把它加一)以後,把結果寫回去以前的空隙,處理器二也有可能去讀它。結果致使雖然兩個處理器都去增長這個計數器,但最終計數器的值只被加了1,其中一個加法運算「丟失」了。併發

原子操做偏偏就是用來防止這個問題的。若是咱們使用一個原子的自加操做(說得更通用一點,原子加法)而不是常規的自加操做,執行指令的處理器會確保上面的三個步驟(讀、加、寫)像一條指令那樣完成,成爲一個原子操做。在自加操做進行的時候,其餘處理器沒法插手。ide


什麼是CAS

比較並交換 Compare and Swap工具

CAS操做須要輸入兩個數值,一箇舊值(指望操做前的值)和一個新值,在操做期間先比較下舊值有沒有發生變化,若是沒有發生變化,才交換成新值,發生了變化則不交換。atom


使用循環CAS實現原子操做

JVM中的CAS操做正是利用了處理器提供的CMPXCHG指令實現的。spa

自旋CAS實現的基本思路就是循環進行CAS操做直到成功爲止,如下代碼實現了一個基於CAS線程安全的計數器方法safeCount和一個非線程安全的計數器count。線程

package com.usoft;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger atomicI = new AtomicInteger(0);
    private int i = 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 Runnable() {
                @Override
                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 (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(cas.i);
        System.out.println(cas.atomicI.get());
        System.out.println(System.currentTimeMillis() - start);

    }

    /**
     * 使用CAS實現線程安全計數器
     */
    private void safeCount() {
        for (;;) {
            int i = atomicI.get();
            boolean suc = atomicI.compareAndSet(i, ++i);
            if (suc) {
                break;
            }
        }
    }

    /**
     * 非線程安全計數器
     */
    private void count() {
        i++;
    }
}

從Java1.5開始JDK的併發包裏提供了一些類來支持原子操做,如AtomicBoolean(用原子方式更新的 boolean 值),AtomicInteger(用原子方式更新的 int 值),AtomicLong(用原子方式更新的 long 值),這些原子包裝類還提供了有用的工具方法,好比以原子的方式將當前值自增1和自減1。翻譯

CAS雖然很高效的解決原子操做,可是CAS仍然存在三大問題。ABA問題,循環時間長開銷大和只能保證一個共享變量的原子操做。

ABA問題。由於CAS須要在操做值的時候檢查下值有沒有發生變化,若是沒有發生變化則更新,可是若是一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,可是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。

從Java1.5開始JDK的atomic包裏提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法做用是首先檢查當前引用是否等於預期引用,而且當前標誌是否等於預期標誌,若是所有相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。

public boolean compareAndSet

        (V      expectedReference,//預期引用

         V      newReference,//更新後的引用

        int    expectedStamp, //預期標誌

        int    newStamp) //更新後的標誌

循環時間長開銷大。自旋CAS若是長時間不成功,會給CPU帶來很是大的執行開銷。若是JVM能支持處理器提供的pause指令那麼效率會有必定的提高,pause指令有兩個做用,第一它能夠延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它能夠避免在退出循環的時候因內存順序衝突(memory order violation)而引發CPU流水線被清空(CPU pipeline flush),從而提升CPU的執行效率。

只能保證一個共享變量的原子操做。當對一個共享變量執行操做時,咱們可使用循環CAS的方式來保證原子操做,可是對多個共享變量操做時,循環CAS就沒法保證操做的原子性,這個時候就能夠用鎖,或者有一個取巧的辦法,就是把多個共享變量合併成一個共享變量來操做。好比有兩個共享變量i=2,j=a,合併一下ij=2a,而後用CAS來操做ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你能夠把多個變量放在一個對象裏來進行CAS操做。


使用鎖機制實現原子操做

鎖機制保證了只有得到鎖的線程可以操做鎖定的內存區域。JVM內部實現了不少種鎖機制,有偏向鎖,輕量級鎖和互斥鎖,有意思的是除了偏向鎖,JVM實現鎖的方式都用到的循環CAS,當一個線程想進入同步塊的時候使用循環CAS的方式來獲取鎖,當它退出同步塊的時候使用循環CAS釋放鎖。詳細說明能夠參見文章Java SE1.6中的Synchronized。

http://www.infoq.com/cn/articles/java-se-16-synchronized

==========END==========

相關文章
相關標籤/搜索