爲啥先說JMM,由於CAS的實現類中維護的變量都被volatile修飾, 這個volatile 是遵循JMM規範(不是百分百遵循,下文會說)實現的保證多線程併發訪問某個變量實現線程安全的手段java
一連串的知識點慢慢縷c++
首先說什麼是JMM, JMM就是你們所說的java的內存模型, 它是人們在邏輯上作出的劃分, 或者能夠將JMM當成是一種規範, 有哪些規範呢? 以下安全
JVM運行的實體是線程, 每個線程在建立以後JVM都會爲其建立一個工做空間, 這個工做空間是每個線程之間的私有空間, 而且任何兩條線程之間的都不能直接訪問到對方的工做空間, 線程之間的通訊,必須經過共享空間來中轉完成多線程
JMM規定全部的變量所有存在主內存中,主內存是一塊共享空間,那麼若是某個線程相對主內存中共享變量作出修改怎麼辦呢? 像下面這樣:併發
JMM還規定以下:函數
問題引入性能
這時候若是多個線程併發按照上面的三步走去訪問主內存中的共享變量的話就會出現線程安全性的問題, 好比說 如今主內存中的共享變量是c=1, 有AB兩個線程去併發訪問這個c變量, 都想進行c++, 如今A將c拷貝到本身的工做空間進行c++, 因而c=2 , 於此同時線程B也進行c++, c在B的工做空間中=2, AB線程將結果寫回工做空間最終的結果就是2, 而不是咱們預期的3優化
相信怎麼解決你們都知道, 就是使用JUC,中的原子類就能規避這個問題this
而原子類的底層實現使用的就是CAS技術atom
CAS(compare and swap) 顧名思義: 比較和交換,在JUC中原子類的底層使用的都是CAS無鎖實現線程安全,是一門很炫的技術
以下面兩行代碼, 先比較再交換, 即: 若是從主內存中讀取到的值爲4就將它更新爲2019
AtomicInteger atomicInteger = new AtomicInteger(4); atomicInteger.compareAndSet(4,2019);
跟進AtomicInteger
的源碼以下, 底層維護着一個int 類型的 變量, (固然是由於我選擇的原來類是AtomicInteger類型), 而且這個int類型的值被 volatile 修飾
private volatile int value; /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; }
volatile是JVM提供的輕量的同步機制, 爲何是輕量界別呢? , 剛纔在上面說了JMM規範中提到了三條特性, 而JVM提供的volatile僅僅知足上面的規範中的 2/3, 以下:
單獨的volatile是不能知足原子性的,即以下代碼在多線程併發訪問的狀況下依然會出現線程安全性問題
private volatile int value; public void add(){ value++; }
那麼JUC的原子類是如何實現的 能夠知足原子性呢? 因而就不得不說本片博文的主角, CAS
咱們跟進AtomicInteger
中的先遞增再獲取的方法 incrementAndGet()
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
經過代碼咱們看到調用了Unsafe類來實現
什麼是Unsafe類?
進入Unsafe類,能夠看到他裏面存在大量的 native方法,這些native方法所有是空方法,
這個unsafe類其實至關於一個後門,他是java去訪問調用系統上 C C++ 函數類庫的方法 以下圖
繼續跟進這個方法incrementAndGet()
因而咱們就來到了咱們的主角方法, 關於這個方法卻是不難理解,主要是搞清楚方法中的var12345到底表明什麼就行, 以下代碼+註釋
var1: 上一個方法傳遞進來的: this,即當前對象 var2: 上一個方法傳遞進來的valueOffset, 就是內存地址偏移量 經過這個內存地址偏移量我能精確的找到要操做的變量在內存中的地址 var4: 上一個方法傳遞進來的1, 就是每次增加的值 var5: 經過this和內存地址偏移量讀取出來的當前內存中的目標值 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; }
注意它用的是while循環, 相對if(flag){} 這種寫法會多一次判斷, 總體的思路就是 在進行修改以前先進行一次比較,若是讀取到的當前值和預期值是相同的,就自增,不然的話就繼續輪詢修改
經過上面的過程, 其實就能總結出CAS的底層實現原理
補充: CAS經過Native方法的底層實現,本質上是操做系統層面上的CPU的併發原語,JVM會直接實現出彙編層面的指令,依賴於硬件去實現, 此外, 對於CPU的原語來講, 有兩條特性1,一定連續, 2.不被中斷
優勢:
它的底層咱們看到了經過do-while 實現的自旋鎖來實現, 就省去了在多個線程之間進行切換所帶來的額外的上下文切換的開銷
缺點:
什麼是ABA問題
咱們這樣玩, 仍是AB兩個線程, 給AtomicInteger
賦初始值0
A線程中的代碼以下:
Thread.sleep(3000); atomicInteger.compareAndSet(0,2019);
B線程中的代碼以下:
atomicInteger.compareAndSet(0,1); atomicInteger.compareAndSet(1,0);
AB線程同時啓動, 雖然最終的結果A線程能成果的將值修改爲2019,,可是它不能感知到在他睡眠過程當中B線程對數據進行過改變, 換句話說就是A線程被B線程欺騙了
ABA問題的解決--- AtomicStampedRefernce.java
帶時間戳的原子引用, 實現的機制就是經過 原子引用+版本號來完成, 每次對指定值的修改相應的版本號會加1, 實例以下
// 0表示初始化, 1表示初始版本號 AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(0, 1); reference.getStamp(); // 獲取版本號 reference.attemptStamp(1,2); // 期待是1, 若是是1就更新爲2
JUC中咱們能夠找到像AtomicInteger
這樣已經定義好了實現類, 可是JUC沒有給咱們提供相似這樣 AtomicUser
或者 AtomicProduct
這樣自定義類型的原子引用類型啊, 不過java仍然是提供了後門就是 原子引用類型
使用實例:
User user = getUserById(1); AtomicReference<User> userAtomicReference = new AtomicReference<User>(); user.setUsername("張三"); userAtomicReference.compareAndSet(user,user);
歡迎關注我, 會繼續更新筆記