Java并發基礎 - CAS篇

      網友投稿 663 2025-03-31

      CAS(Compare and Swap),中文可以理解為比較并替換,是一種實現并發算法時常用到的技術。它是一種無鎖原子算法,是一種樂觀鎖的實現方式,在操作時是抱著樂觀的態度進行的,它總是認為可以成功完成操作。

      CAS思路

      讓我們先直觀的理解一下CAS的大體思路:

      CAS(V,E,N)

      它包含 3 個參數,V表示要更新變量的值,E表示預期值,N表示新值。僅當 V值等于E值時,才會將V的值設為N,如果V值和E值不同,則說明已經有其他線程做過更新,則當前線程則什么都不做。最后,CAS 返回當前V的真實值。

      先從一個簡單例子開始:

      public class CasTest0 { private static volatile int m = 0; public static void increase1() { m++; } public static void main(String[] args) throws Exception { for (int i = 0; i < 1000; i++) { new Thread(() -> { CasTest0.increase1(); }).start(); } TimeUnit.SECONDS.sleep(3); System.out.println(m); } } public class CasTest0 { private static volatile int m = 0; public static void increase1() { m++; } public static void main(String[] args) throws Exception { for (int i = 0; i < 1000; i++) { new Thread(() -> { CasTest0.increase1(); }).start(); } TimeUnit.SECONDS.sleep(3); System.out.println(m); } }

      運行這個例子,最后打印出的值可能是任意小于1000的正整數。在之前的文章中講過,volatile可以保證可見性和有序性,但是無法保證原子性。而m++這一操作又不是原子操作,可以分為三個步驟:

      獲取靜態變量m的值并入棧,int型常量值1入棧

      棧頂int型數值相加,并將結果壓入棧頂

      靜態變量m賦值

      從上述分析可得,自增操作并不具有原子性,所以在多線程環境下,運行得到的結果必定小于等于1000。

      JUC中的CAS實現

      接下來,換成JUC包下的原子類操作試一下:

      public class CasTest1 { private static AtomicInteger atomicI = new AtomicInteger(0); public static void increase2() { atomicI.incrementAndGet(); } public static void main(String[] args) throws Exception { for (int i = 0; i < 1000; i++) { new Thread(() -> { CasTest1.increase2(); }).start(); } TimeUnit.SECONDS.sleep(3); System.out.println(atomicI.get()); } } public class CasTest1 { private static AtomicInteger atomicI = new AtomicInteger(0); public static void increase2() { atomicI.incrementAndGet(); } public static void main(String[] args) throws Exception { for (int i = 0; i < 1000; i++) { new Thread(() -> { CasTest1.increase2(); }).start(); } TimeUnit.SECONDS.sleep(3); System.out.println(atomicI.get()); } }

      這樣運行結果會始終返回1000,這就取決于原子Integer類AtomicInteger發揮了作用。反編譯看一下實際執行的指定,這里是僅以一條指令完成了自增的操作

      看一下AtomicInteger類的實現:

      在AtomicInteger中使用了Unsafe類,這個類可以說是java提供的一個后門類,可以用來直接操作內存地址。(針對Unsafe,以前寫過一篇專門的文章,大家如果有需求可以看一下Java雙刃劍之Unsafe類詳解)

      代碼中的變量表示的意義如下:

      Java并發基礎 - CAS篇

      valueOffset:變量value的地址偏移量,具體賦值是在下面的靜態代碼塊中進行的

      value:需要修改的值,相當于i++操作中的i

      incrementAndGet最終調用Unsafe類中的方法:

      //獲取內存地址為obj+offset的變量值, 并將該變量值加上delta public final int getAndAddInt(Object obj, long offset, int delta) { int v; do { //通過對象和偏移量獲取變量的值 //由于volatile的修飾, 所有線程看到的v都是一樣的 v= this.getIntVolatile(obj, offset); } while(!this.compareAndSwapInt(obj, offset, v, v + delta)); return v; }

      具體流程:

      while循環中的compareAndSwapInt()方法嘗試修改v的值, 該方法會通過obj和offset獲取變量的值

      如果這個值和v不一樣,說明其他線程修改了obj+offset地址處的值,此時compareAndSwapInt()返回false,繼續循環

      如果這個值和v一樣,說明沒有其他線程修改obj+offset地址處的值,此時可以將obj+offset地址處的值改為v+delta, compareAndSwapInt()返回true,退出循環

      compareAndSwapInt是一個native方法,調用了c++中的方法,后續調用鏈為調用匯編中的cmpxchg指令,最終通過二進制硬件支持實現了這一原子操作。

      那么,CAS都有什么應用場景呢?典型場景就是電商中對于貨物的庫存的管理。首先從數據庫中讀取庫存,在賣出貨物后更新庫存時,判斷庫存數量是否還和自己取出時相同,如果相同則更新,不同則進行自旋直到執行成功。

      ABA問題

      說了這么多,那么CAS就是完美的嗎,很遺憾并不是,CAS仍然存在經典的ABA問題:

      按照我們之前的理解,CAS需要檢查操作值有沒有發生改變,如果沒有發生改變則更新。但是存在這樣一種情況:如果一個值原來是A,變成了B,然后又變成了A,那么在CAS檢查的時候會發現沒有改變,但是實質上它已經發生了改變,這就是所謂的ABA問題。

      public class CasTest2 { private static AtomicInteger atomicI = new AtomicInteger(100); public static void main(String[] args) throws Exception { Thread t1 = new Thread(() -> { System.out.println(Thread.currentThread().getName()+":"+atomicI.compareAndSet(100, 110)); },"thread1"); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName()+":"+atomicI.compareAndSet(110, 100)); } catch (InterruptedException e) { e.printStackTrace(); } } },"thread2"); t2.start(); Thread t3 = new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+":"+atomicI.compareAndSet(100, 90)); } catch (InterruptedException e) { e.printStackTrace(); } },"thread3"); t3.start(); } }

      運行結果:

      三個線程的運行結果都為true,但是thread3執行時候檢查的值是已經被中途修改過的,而不是初始值了。

      應對ABA問題,其解決方案是加上版本號,即在每個變量都加上一個版本號,每次改變時加1。

      即將原來的:A —> B —> A 變成:1A —> 2B —> 3A

      這里引入AtomicStampedReference這個類,它內部不僅維護了對象值,還維護了一個int類型的stamp值,可以將其理解為時間戳或版本號。當AtomicStampedReference對應的數值被修改時,除了更新數據本身外,還必須要更新這個stamp的值。并且當AtomicStampedReference設置對象值時,對象值以及stamp值都必須滿足期望,寫入才會成功。因此,即使對象值被反復讀寫,寫回原值,只要stamp的值發生變化,就能防止不恰當的寫入。

      public class CasTest3 { private static AtomicStampedReference asr = new AtomicStampedReference(100, 1); public static void main(String[] args) throws Exception { Thread t1 = new Thread(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } System.out.println("1:" + asr.compareAndSet(100, 110, asr.getStamp(), asr.getStamp() + 1) ); System.out.println("stamp:"+asr.getStamp()+" value:"+asr.getReference()); System.out.println("2:" + asr.compareAndSet(110, 100, asr.getStamp(), asr.getStamp() + 1) ); System.out.println("stamp:"+asr.getStamp()+" value:"+asr.getReference()); }); Thread t2 = new Thread(() -> { int stamp = asr.getStamp(); try { TimeUnit.SECONDS.sleep(4); } catch (Exception e) { e.printStackTrace(); } System.out.println("3:" + asr.compareAndSet(100, 110, stamp, stamp + 1) ); System.out.println("stamp:"+asr.getStamp()+" value:"+asr.getReference()); }); t1.start(); t2.start(); } }

      運行結果:

      Thread2期待的stamp值為1,Reference的值為100。Thread1在每次自增的同時,stamp值加1,所以在經過Thread1兩次修改Reference值后,即使與期望的Reference值相同,但stamp值不同,仍然不做任何修改。

      CAS缺點

      最后對CAS的缺點進行一下總結,CAS雖然高效地解決了原子操作問題,但是還是存在一些缺陷的,主要表現在三個方面:

      循環時間太長:如果CAS一直不成功的情況發生,會一直進行自旋操作,會造成大量CPU執行開銷。在JUC中有些地方就限制了CAS自旋的次數,例如BlockingQueue的SynchronousQueue

      只能保證一個共享變量原子操作:看了CAS的實現過程,可以得出CAS只能針對一個共享變量,如果是多個共享變量情況,只能使用鎖來保證原子性了

      ABA問題:CAS需要檢查操作值有沒有發生改變,如果沒有發生改變則更新,但是之前提到的ABA問題會造成一定影響,這時只要加上版本號對其進行限定就可以了

      Java 多線程

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:免費甘特圖(甘特圖在線生成)
      下一篇:Excel對不規則大小不一致的區域進行合并單元格的批量操作(大小不一的不規則合并單元格怎么批量求和)
      相關文章
      国产精品亚洲一区二区三区| 亚洲欧洲国产成人精品| 久久精品国产亚洲一区二区| 亚洲人成综合在线播放| 亚洲一区二区三区在线观看精品中文| 亚洲日韩在线中文字幕综合 | 亚洲福利秒拍一区二区| 国产精一品亚洲二区在线播放| 在线日韩日本国产亚洲| 激情97综合亚洲色婷婷五| 亚洲一区二区精品视频| 亚洲午夜精品第一区二区8050| 亚洲精品成人a在线观看| 亚洲精品线路一在线观看| 亚洲精品国产日韩无码AV永久免费网 | 国产专区一va亚洲v天堂| 亚洲国产中文字幕在线观看| 五月婷婷亚洲综合| 亚洲AV永久无码精品一区二区国产| 无码色偷偷亚洲国内自拍| 国产亚洲精品美女久久久久久下载| 日韩国产精品亚洲а∨天堂免| 香蕉视频亚洲一级| 午夜亚洲av永久无码精品| 亚洲无码黄色网址| 亚洲精品二区国产综合野狼| 亚洲av永久无码精品网站| 老司机亚洲精品影院无码| 精品亚洲成a人片在线观看| 亚洲精品动漫在线| 国产成人亚洲合集青青草原精品| 亚洲AV男人的天堂在线观看| 亚洲爆乳无码精品AAA片蜜桃| 色综合久久精品亚洲国产| 国产尤物在线视精品在亚洲| 国产亚洲日韩一区二区三区| 精品久久香蕉国产线看观看亚洲| 亚洲∧v久久久无码精品| 亚洲综合免费视频| 狠狠色伊人亚洲综合网站色| 亚洲欧美在线x视频|