學習狀況記錄java
記錄在學習線程安全知識點中,關於CAS
的有關知識點。算法
線程安全是指:多個線程無論以何種方式訪問某個類,而且在主調代碼中不須要進行同步,都能表現正確的行爲。安全
常見的線程安全實現方法分爲不可變對象、線程互斥同步、非阻塞同步、線程本地存儲等方案,本文要講的就是非阻塞同步中的核心CAS
.多線程
從處理問題的方式上說,互斥同步屬於一種悲觀的併發策略。併發
隨着硬件指令集的發展,咱們能夠採用基於衝突檢查的樂觀併發策略,通俗地說,就是先行操做,若是沒有其餘線程爭用共享數據,那操做就成功了;若是共享數據有爭用,產生了衝突,那就再採起其餘的補償措施(最多見的補償措施就是不斷地重試,直到成功爲止),這種樂觀的併發策略的許多實現偶讀不須要把線程掛起,所以這種同步操做稱爲非阻塞同步。less
樂觀鎖須要操做和衝突檢測這兩個步驟具有原子性,這裏就不能再使用互斥同步來保證了,只能靠硬件來完成。硬件支持的原子性操做最典型的是:比較並交換(Compare-and-Swap,CAS)。CAS 指令須要有 3 個操做數,分別是內存地址 V、舊的預期值 A 和新值 B。當執行操做時,只有當 V 的值等於 A,纔將 V 的值更新爲 B。ide
各類Atomic開頭的原子類,內部都應用到了CAS
。就拿AtomicInteger
爲例。性能
J.U.C 包裏面的原子類 AtomicInteger
的方法調用了 Unsafe
類的 CAS
操做。學習
看看AtomicInteger
對象一次自增,CAS
起了什麼做用,如下代碼是 incrementAndGet()
的源碼,能夠看到內部調用了 Unsafe
對象的 getAndAddInt()
。spa
如下代碼是 getAndAddInt()
源碼,var1
指示對象內存地址,var2
指示該字段相對對象內存地址的偏移,var4
指示操做須要加的數值,這裏爲 1。經過 getIntVolatile(var1, var2)
獲得舊的預期值,經過調用 compareAndSwapInt()
來進行 CAS
比較,若是該字段內存地址中的值等於 var5
,那麼就更新內存地址爲 var1+var2
的變量爲 var5+var4
。
compareAndSwapInt(var1, var2, var5, var5 + var4
其實換成compareAndSwapInt(obj, offset, expect, update)
比較清楚,意思就是若是obj
內的value
和expect
相等,就證實沒有其餘線程改變過這個變量,那麼就更新它爲update
,若是這一步的CAS
沒有成功,那就採用自旋的方式繼續進行CAS操做,取出乍一看這也是兩個步驟了啊,其實在JNI裏是藉助於一個CPU指令完成的。因此仍是原子操做。
ABA問題
循環時間長開銷大
CAS
(也就是不成功就一直循環執行直到成功)若是長時間不成功,會給CPU帶來比較大的執行開銷。只能保證一個共享變量的原子操做
CAS
只對單個共享變量有效,當操做涉及跨多個共享變量時CAS
無效。可是從 JDK 1.5開始,提供了AtomicReference
類來保證引用對象之間的原子性,你能夠把多個變量放在一個對象裏來進行 CAS
操做.因此咱們可使用鎖或者利用AtomicReference
類把多個共享變量合併成一個共享變量來操做。使用 CAS 原子指令來處理對數據的併發訪問,這是非阻塞算法得以實現的基礎。關於非阻塞算法是屬於J.U.C中併發容器部分的知識,屬於比較難的內容。目前先引用幾篇文章。做爲記錄。