問題一:java中的CAS是什麼?
問題二:爲什麼要使用CAS?
問題三:CAS使用中需要注意什麼問題?
這裏以提問的方式引出話題,下面帶大家慢慢了解CAS。
1.CAS的含義
CAS是compare and swap的縮寫,即我們所說的比較交換。cas是一種基於鎖的操作,而且是樂觀鎖。在java中鎖分爲樂觀鎖和悲觀鎖。悲觀鎖是將資源鎖住,等一個之前獲得鎖的線程釋放鎖之後,下一個線程纔可以訪問。而樂觀鎖採取了一種寬泛的態度,通過某種方式不加鎖來處理資源,比如通過給記錄加version來獲取數據,性能較悲觀鎖有很大的提高。
CAS 操作包含三個操作數 —— 內存位置(V)、預期原值(A)和新值(B)。如果內存地址裏面的值和A的值是一樣的,那麼就將內存裏面的值更新成B。CAS是通過無限循環來獲取數據的,若果在第一輪循環中,a線程獲取地址裏面的值被b線程修改了,那麼a線程需要自旋,到下次循環纔有可能機會執行。
2.CAS的問題
①.CAS容易造成ABA問題。一個線程a將數值改成了b,接着又改成了a,此時CAS認爲是沒有變化,其實是已經變化過了,而這個問題的解決方案可以使用版本號標識,每操作一次version加1。在java5中,已經提供了AtomicStampedReference來解決問題。
②.CAS造成CPU利用率增加。之前說過了CAS裏面是一個循環判斷的過程,如果線程一直沒有獲取到狀態,cpu資源會一直被佔用。
3.看AutoInteger的實現源碼
其實AutoInteger就是使用了CAS來實現加1,我們知道如果有一個共享變量count=1,開5個線程,每個線程加20次,結果一般來說都會小於100.
@Test public void test20() throws InterruptedException { for (int i = 1; i <= 5; i++) { MyThrend thrend = new MyThrend("thead" + i); Thread thread = new Thread(thrend); thread.start(); } Thread.sleep(2000); System.out.println(MyCount1.count); } static class MyThrend implements Runnable { private String name; MyThrend(String threadName) { this.name = threadName; } @Override public void run() { for (int i=0;i<20;i++) MyCount1.count++; } } private static class MyCount1 { static int count = 0; }
結果78
現在修改一個代碼,將int變成AtomicInteger
@Test public void test20() throws InterruptedException { for (int i = 1; i <= 5; i++) { MyThrend thrend = new MyThrend("thead" + i); Thread thread = new Thread(thrend); thread.start(); } Thread.sleep(2000); System.out.println(MyCount.count.get()); } static class MyThrend implements Runnable { private String name; MyThrend(String threadName) { this.name = threadName; } @Override public void run() { for (int i=0;i<20;i++) MyCount.count.getAndIncrement(); //加1方法 } } private static class MyCount { static AtomicInteger count = new AtomicInteger(0); }
每次結果都是100,怎麼做到的呢?這裏是沒有直接加鎖的,看源碼。
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); //第一個參數當前對象地址,第二個參數數據偏移量,第三個參數每次指定默認加1 }
public final int getAndAddInt(Object var1, long var2, int var4) { //這個方法使用的就是CAS,核心在於循環比較內存裏面的值和當前值是否相等,如果相等就用新值覆蓋 int var5; do { var5 = this.getIntVolatile(var1, var2); //如果a,b線程同時執行這個方法,a線程拿到值1後cpu執行時間到了掛起,b開始執行,也拿到1,但是沒有掛起,接着將值變成了2 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); //這個時候a線程恢復執行,去比較的時候發現手上的1 和內存裏面的值2不等,這個時候他要進行下一個循環,看出來了佔用cpu吧 return var5; }AtomicInteger,AtomicLong,AtomicBoolean.....都在java.util.current.atomic包下面,採用了CAS機制來實現加鎖,裏面具體方法就不看了,留給大家後續研究!