CAS 全稱是 compare and swap,是一種用於在多線程環境下實現同步功能的機制。java
CAS 它是一條CPU併發原語。操做包含三個操做數 -- 內存位置、預期數值和新值。CAS 的實現邏輯是將內存位置處的數值與預期數值想比較,若相等,則將內存位置處的值替換爲新值。若不相等,則不作任何操做。這個過程是原子的。多線程
CAS併發原語體如今java語言中的sun.misc.Unsafe類中的各個方法。調用Unsafe類中的CAS方法,JVM會幫咱們實現彙編指令。這是一種徹底依賴硬件的功能,經過它實現了原子操做。因爲CAS是一種系統原語,原語屬於操做系統用語範疇,是由若干條指令組成的,用於完成某個功能的一個過程,而且原語的執行必須是連續的,在執行過程當中不容許被打斷,也就是說CAS是一條CPU的原子指令,不會形成所謂的數據不一致問題。併發
Unsafe類是CAS的核心類,因爲Java方法沒法直接訪問底層系統,須要經過本地(native)方法來訪問,基於該類能夠直接操做特定內存的數據。Unsafe類存在與sum.misc包中,其內部方法操做能夠像C的指針同樣直接操做內存,由於Java中CAS操做的執行依賴於Unsafe類的方法。源碼分析
Unsafe類中的全部方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操做系統底層資源執行相應任務。優化
public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(5); // 運行結果: true 2019 System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t" + atomicInteger.get()); // 運行結果: false 2019 System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t" + atomicInteger.get()); // 此方法能夠解決多線程環境下i++問題,底層使用的是Unsafe類CAS和自旋鎖 atomicInteger.getAndIncrement(); } }
源碼分析:this
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { // this=當前對象 valueOffset=內存偏移量(內存地址) 1=固定值,每次調用+1 // Unsafe就是根據內存偏移地址獲取數據的。 return unsafe.getAndAddInt(this, valueOffset, 1); } /** * 爲了方便查看和添加註釋,此方法是從Unsafe類中複製出來的 */ public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { // 獲取var1對象,內存地址在var2的值。 // 至關於這個線程從主物理內存中取值copy到本身的工做內存中。 var5 = this.getIntVolatile(var1, var2); // 比較並交換,若是var1對象,內存地址在var2的值和var5值同樣,那麼就+1 // compareAndSwapInt若是返回true,取反爲false,說明更新成功,退出循環,則返回。 // compareAndSwapInt若是返回false,取反爲true,說明當前線程工做內存中的值和主物理內存中的值不同,被其餘線程修改了,則繼續循環獲取比較,直到更新成功爲止。 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
執行過程說明:atom
volatile簡單說明:
volatile是一個輕量級的同步機制, 三大特性: 保證可見性, 不保證原子性, 禁止指令重排。操作系統
優勢:線程
缺點:指針
舉個栗子說明:
主內存有個數據值:A,兩個線程A和B分別copy主內存數據到本身的工做區,A執行比較慢,須要10秒, B執行比較快,須要2秒, 此時B線程將主內存中的數據更改成B,過了一會又更改成A,而後A線程執行比較,發現結果是A,覺得別人沒有動過,而後執行更改操做。其實中間已經被更改過了,這就是ABA問題。
也就是ABA問題只要開始時的數據和結束時的數據一致,我就認爲沒改過,無論過程。
儘管A線程的CAS操做是成功的,可是不表明這個過程就是沒問題的。
ABA問題說簡單點就是,預判值仍是和當初抓取的同樣,可是這個「 值 」的版本可能不同了,在某些不只要考慮數據值是否一致,還要考慮版本是否一致的場景下須要注意.
Java併發包爲了解決這個問題,提供了一個帶有標記的原子引用類「AtomicStampedReference」,它能夠經過控制變量值的版原本保證CAS的正確性。
/** * 解決CAS的ABA問題 */ public class SolveABAOfCAS { static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1); public static void main(String[] args) throws InterruptedException { System.out.println("==========如下是ABA問題的產生=========="); new Thread(() -> { atomicReference.compareAndSet(100, 101); atomicReference.compareAndSet(101, 100); }, "t1").start(); new Thread(() -> { try { // 暫停1秒鐘,保證上面完成一次ABA操做 Thread.sleep(1000); System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get()); } catch (InterruptedException e) { e.printStackTrace(); } }, "t2").start(); Thread.sleep(2000); System.out.println("==========如下是ABA問題的解決=========="); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + "\t第1次版本號" + stamp); try { // 暫停一秒鐘t3線程 Thread.sleep(1000); atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "\t第2次版本號" + atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "\t第3次版本號" + atomicStampedReference.getStamp()); } catch (InterruptedException e) { e.printStackTrace(); } }, "t3").start(); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + "\t第1次版本號" + stamp); try { // 暫停3秒鐘t4線程,保證上面的t3線程完成一次ABA操做 Thread.sleep(3000); boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1); System.out.println(Thread.currentThread().getName() + "\t修改爲功否: " + result + "\t當前最新實際版本號: " + atomicStampedReference.getStamp()); System.out.println(Thread.currentThread().getName() + "\t當前實際最新值: " + atomicStampedReference.getReference()); } catch (InterruptedException e) { e.printStackTrace(); } }, "t4").start(); } }
若是以爲對你有幫助,歡迎來訪個人博客:http://jianjieming.com