天天看點

多線程程式設計--08java原子操作的實作原理

這一篇記錄java保證原子操作的原理

    一、使用循環CAS實作原子操作

JVM中的CAS操作正是利用了處理器提供的CMPXCHG指令實作的。自旋CAS實作的基本思路就是循環進行CAS操作直到成功為止;

從java1.5開始,JDK的并發包裡提供了一些類用來支援原子操作,如AtomicBoolean、AtomicInteger…(主要是資料類型)。這些原子包裝類還提供了有用的工具方法,比如以原子的方式将目前值自增1和自減1。

    二、使用CAS實作原子操作的三大問題

在java并發包中有一些并發架構也使用了自旋CAS的方式來實作原子操作,比如LinkedTransferQueue類的Xfer方法。CAS雖然很高效的解決了 原子操作,但是CAS仍然存在三大問題:1、A->B->A問題;2、循環時間長開銷大;3、隻能保證一個共享變量的原子操作。

    1)A->B->A問題。因為CAS需要在操作值得時候,檢查值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成B,又變回A。那麼使用CAS進行檢查時會發現他的值沒有發生變化,但是實際上卻變化了。A->B->A問題的解決思路就是使用版本号。在變量前面追加上版本号,每次變量更新的時候把版本号加1,這樣A->B->A就會變成1A->2B->3A。從java1.5開始,JDK的Atomic包裡提供了一個類AtomicStampedRefererce來解決A->B->A問題。這個類的compareAndSetf方法的作用首先檢查目前引用是否等于預期引用,并且檢查目前标志是否等于預期标志,如果全部相等,則以原子方式将該應用和該标志的值設定為給定的更新值。

    2)循環時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支援處理器提供的pause指令,那麼效率會有一定的提升。pause指令有兩個作用:

         1可以延遲流水線執行指令,使CPU不會消耗過多的執行資源,延遲的時間取決于具體實作的版本;

         2可以避免在退出循環的時候因記憶體順序沖突而引起流水線被清空,進而提高CPU的執行效率。

    3)隻能保證一個共享變量的原子操作。當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖。還有一個取巧的方法,就是把多個共享變量合并成一個共享變量來操作。比如,有兩個共享變量i=2,j=a,合并一下ij=2a,然後用CAS來操作ij。從 Java1.5開始,JDK提供了AtomicReference類來保證引用對象之間的原子性,就可以把多個變量放在一個對象裡來進行CAS操作。

三、使用鎖機制實作原子操作

    鎖機制保證了隻有獲得鎖的線程才能夠操作鎖定的記憶體區域。JVM内部實作了很多種鎖機制諸如偏向鎖、輕量級鎖和互斥鎖。除偏向鎖以外,JVM實作鎖的方式都用了循環CAS,也就是說當一個線程想進入同步塊的時候使用循環CAS的方式來擷取鎖,當退出痛不快的時候使用循環CAS來釋放鎖。