讀書筆記之Java併發編程中的原子操作

前言

今天的筆記我們繼續讀《Java併發編程的藝術-第二章》,來了解一下原子操作以及Java中如何實現原子操作。

概念

原子(atomic)本意是“不能被進一步分割的最小粒子”,而原子操作(atomic operation)意爲“不可被中斷的一個或一系列操作”。

處理器實現原子操作

處理器會保證基本內存操作的原子性。處理器保證從系統內存中讀取或者寫入一個字節是原子的,意思是當一個處理器讀取一個字節時,其他處理器不能訪問這個字節的內存地址。最新的處理器能自動保證單處理器進行16/32/64位的操作是原子的,並且提供總線鎖定和緩存鎖定兩個機制來保證複雜內存操作的原子性。

使用總線保證原子性

如果有多個處理器同時對共享變量進行操作,那麼共享變量就會被多個處理器同時操作,這樣的話,讀改寫操作就不是原子的。比如i=1,i++,兩個處理器同時進行操作,最後的結果,可能是3,也可能是3.原因可能是多個處理器同時從各自的緩存中讀取變量i,分別進行加1操作,然後分別寫入系統內存。處理器使用總線鎖來解決這個問題。當處理器發出LOCK#信號時,其他處理器的請求會被阻塞主,該處理器可以獨佔共享內存。

使用緩存鎖定來保證原子性

鎖總線開銷還是很大的,鎖住了CPU和內存之間的通信。因爲頻繁使用的內存會緩存在處理器的L1、L2、L3高速緩存中,原子操作可以在緩存內部完成,同時通過緩存一致性協議,當A處理器修改緩存中的i時,其他處理器不能同時緩存i,即會使得其他處理器中對於共享變量的緩存失效。這段還不是特別明白,感覺得重新翻一下操作系統,有知道的網友可以留言補充一下。

Java實現原子操作的方式

Java可以使用鎖,實現一段代碼的原子操作。但這樣開銷比較大,會引起頻繁的上下文切換。另外一種方式就是使用CAS操作(比較交換)。CAS算法的過程是比較簡單的。它會包含三個參數(V,E,N))。V表示要更新的變量,E表示預期值,N值。當且僅當V等於E值時,纔會將V的值設爲N,如果V值和E值不同,說明已經有其他線程做了更新,則當前線程什麼都不做。當多個線程同時使用CAS對變量進行操作時,只有一個會勝出併成功更新,其餘會失敗。失敗的線程不會被掛起。Java中對於基本類型的包裝類都有對應的原子操作實現,比如AtomicBoolean,AtomicInteger等。如果拿AtomicInteger爲例子,其中的incrementAndGet的實現如下所示,是直接調用了Unsafe類的方法:

 public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

其中var1是傳入對象的引用,var2是字段到對象頭部的偏移量,方便快速定位,var5是當前值,var5+var4就是期望值,var4傳入的是1。如果是引用類型的話,可以使用AtomicReference。CAS操作雖好,但它會遇到ABA問題,即一個變量先是A,後來變成了B,在比較時又變回了A,但CAS操作無法感知到這種情況,如果說我們是否可以修改當前值,不僅取決於當前值,還取決於它的變化,那麼原有的CAS操作就無能爲力了,因爲它感知不到。貼心的JDK爲我們提供了AtomicStampedReference,它在對象內部維護了時間戳,當更新數據時,不僅要更新數據,還要更新時間戳。當AtomicStampedReference設置對象值時,對象值以及時間戳都必須滿足期望值,寫入纔會成功。如果是數組類型的話,JDK提供了AtomicIntegerArray等數組類型的原子類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章