1. 程式人生 > >Java併發中原子操作的實現

Java併發中原子操作的實現

處理器實現原子操作

如果多個處理器同時對共享變數進行讀改寫(i++就是經典的讀改寫操作)操作,那麼共享變數就會被多個處理器同時進行操作,這樣讀改寫操作就不是原子的,所以要保證操作是原子性,則必須保證同一時刻只有一個處理器訪問共享變數的記憶體地址

  1. 使用匯流排鎖定保證原子性

    處理器在總線上輸出一個LOCK#訊號,那麼其他處理器的請求將被阻塞,此時該處理器就可以獨佔共享記憶體,從而保證只有一個處理器操作共享記憶體。

  2. 使用快取鎖定保證原子性
    匯流排鎖把CPU和記憶體之間通訊鎖住了,這使得鎖定期間,其他處理器不能操作其他記憶體地址的資料,所以匯流排鎖定的開銷比較大,現在多用快取鎖來代替匯流排鎖優化。如果快取在處理器快取行中的記憶體區域在LOCK操作期間被鎖定,當它執行鎖操作回寫記憶體時,處理器不在總線上聲言LOCK#訊號,而是修改內部的記憶體地址
    ,其他處理器通過嗅探在總線上傳播的資料來檢查自己快取的值是不是過期,如果過期,就會將該快取行設定成無效狀態,從而避免多個處理器同時修改該共享記憶體。

JAVA如何實現原子操作

1.使用迴圈CAS實現原子操作

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

可能會遇到的問題:

ABA問題。因為CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變數前面追加上版本號,每次變數更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。
從Java1.5開始JDK的atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。

迴圈時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。

只能保證一個共享變數的原子操作。當對一個共享變數執行操作時,我們可以使用迴圈CAS的方式來保證原子操作,但是對多個共享變數操作時,迴圈CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變數合併成一個共享變數來操作。比如有兩個共享變數i=2,j=a,合併一下ij=2a,然後用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用物件之間的原子性,你可以把多個變數放在一個物件裡來進行CAS操作。

2.使用鎖機制實現原子操作

鎖機制保證了只有獲得鎖的執行緒能夠操作鎖定的記憶體區域。JVM內部實現了很多種鎖機制,有偏向鎖,輕量級鎖和互斥鎖,除了偏向鎖,JVM實現鎖的方式都用到的迴圈CAS,當一個執行緒想進入同步塊的時候使用迴圈CAS的方式來獲取鎖,當它退出同步塊的時候使用迴圈CAS釋放鎖。