目錄
一、CAS的簡單介紹
CAS邏輯(用偽代碼來描述)
二、CAS在多線程中簡單的使用
三、原子類自增的代碼分析
都看到這了,點個贊再走吧,謝謝謝謝謝
一、CAS的簡單介紹
CAS的全稱:“Compare And Swap”,字面意思是 “比較和交換”;一個CAS涉及到以下操作,有兩個寄存器:A,SwapB,還有內存的值:AddressC。
先判斷寄存器A是否和AddressC的值相同,相同,SwapB的值和AddressC的值進行交換,返回成功操作,否則返回失敗操作。這里的交換值我們也可以理解成賦值操作,因為寄存器中的值我們不關心,用完就丟掉了,只關心內存中的值。
CAS邏輯(用偽代碼來描述)
偽代碼:
這里有兩個寄存器:expectValue,swapValue,還有內存的值:address,初始化如圖:
進入if語句,判斷內存的值和寄存器e的值是否相等,如果相等,就交換寄存器swap和內存address的值,如圖:
如圖:
然后返回true,如果if條件不成立則返回false。
在計算機中,上述操作在計算機只是一條指令,因為單個指令的原因,所以CAS指令是原子的。
CAS指令不涉及到加鎖,阻塞。基于CAS指令,合理使用的話,在多線程中,我們可以實現無鎖編程;因為之前我們討論并發編程的線程安全問題時,是通過加鎖,阻塞這樣方式解決線程安全問題,因為會有阻塞,所以性能也就會降低,用CAS指令實現無鎖編程,也能保證線程安全,不涉及到阻塞,這樣性能就能得到很大的提升,在多線程編程中打開了新世界的大門。
二、CAS在多線程中簡單的使用
因為CAS是CPU的指令,有的cpu可能不支持CAS,但主流的CPU(x86,arm...)都是支持的。
CAS本身是CPU的指令,操作系統對其做了封裝,jvm又對操作系統提供的api又做了一層封裝。java中CAS的api是放在unsafe中的,這個包名的意思,顧名思義也是“不安全”的意思,一般在java中不建議使用,java的標準庫中,對于CAS進行了進一步的封裝,把CAS的一些操作封裝成工具類,供程序猿使用。而主要的一個工具,叫做 “原子類”。
在 java.util.concurrent.atomic? 包下,里面的類就是基于上述方式實現的,典型的類就是AtomicInteger類,里面有很多方法可以實現數值的自增、自減,以及基本的加減操作。下面的代碼案例也是使用AtomicInteger展示。
我們以前寫過一個代碼,讓兩個線程實現一個變量自增10_0000次,如下代碼是線程不安全。
public class AtomicIntegerTest1 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}
執行結果:
輸出結果也和我們預期結果不同,肯定是線程不安全的,而原因就是因為count++操作不是原子的,在計算機中有三個指令。
這里,我們使用CAS的方式,來實現讓兩個線程實現一個變量自增10_0000次,代碼如下
public class AtomicIntegerTest1 {public static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.getAndIncrement();//和count++意思一樣}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.getAndIncrement();//和count++意思一樣}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}
執行結果:
因為這里用CAS的方式,原本count++操作在計算機中有3條指令,不是原子的,肯定線程不安全;但是用CAS的方式就可以把像count++這樣的操作,用一條指令完成,是原子的,在這里就不涉及到線程安全問題了。
AtomicInteger中有很多方法:自增,自減,+=,-=等待,這里就不展開討論了。
三、原子類自增的代碼分析
代碼還是上述的代碼,如下:
public class AtomicIntegerTest1 {public static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.getAndIncrement();//和count++意思一樣}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.getAndIncrement();//和count++意思一樣}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}
在標準庫中,getAndIncrement方法是這樣的:
這里比較難理解,我們用偽代碼的形式介紹,偽代碼如下:
當有兩個線程對同一個變量進行自增操作時,執行流程是這樣的:
這里的oldValue是寄存器中的值,它的值就是AtomicInteger括號里面初始化的值,value是內存中的值。從上到下,把內存value的值都賦值給oldValue,兩個線程拿到的都是同一個內存value的值。
這時,右邊的線程while循環里的判斷,先執行CAS這里的操作,如圖:
????????
執行完CAS操作后返回true,然后while循環里true != ture,條件不成立,不執行while循環內的代碼。
當左邊線程進入while循環里面的判斷語句時,如圖:
也會進入CAS操作,這里因為內存value的值修改了,當前線程寄存器oldValue值還是0,給oldValue+1,會返回false,這時循環條件false != true成立,就會執行oldValue = value操作,如圖:
再次進入循環條件里面執行CAS操作,這時候value == oldValue,所以會讓oldvalue+1賦值給value,這時候value的值就是2了,如圖:
所以,兩個線程不管順序是啥樣的,使用getAndIncrement方法都不會出現線程安全問題,因為CAS操作本身就是原子的,原因的邏輯理解也大概是下面這樣的:
如果cas不成功,會重復上面的操作,再次讀取數據,這次讀取到的數據就是正確的了,cas也就能成功。意思就是這個方法里面會判斷拿到的值是不是最新值,如果不是就去拿最新的值,再去CAS,這時候因為拿到的是最新值,所以這時能CAS成功。