1. 程式人生 > 其它 >java的原子類到底是啥?ABA,CAS又是些什麼?

java的原子類到底是啥?ABA,CAS又是些什麼?

1)解決併發不是用鎖就能解決嗎,那SDK幹嘛還要搞個原子類出來?

  • 鎖雖然能解決,但是加鎖解鎖始終還是對效能是有影響的,並且使用不當可能會造成死鎖之類的問題。

2)原子類是怎樣使用的,比如說我要實現一個執行緒安全的累加器?

 public class Test {
   AtomicLong count =
     new AtomicLong(0); // 原子類變數count
   void add10K() {
     int idx = 0;
     while(idx++ < 10000) {
       count.getAndIncrement();// 原子操作
    }
  }
 }

3)原子類是無鎖的,那他底層是靠什麼來實現原子安全的?

  • 靠硬體。我們的CPU為了解決併發問題,提供了CAS指令,而CPU的指令本身就是原子性的。

4)什麼是CAS呢?

  • compare and swap ,他主要就看3個引數,A是共享變數的記憶體地址,B是用於和原地址值比較的,C是我們要更新的值。

  • 其實就是把原來的共享變數的值取出一份來,然後你要更新的話,得對比一下,當前的值和我取出來儲存的這份值是不是相同的,如果是相同的,那就可以修改,不同的話說明被別人修改過了,那你現在就不能更新。

     
     class SimulatedCAS{
       int count;
       synchronized int cas(
         int expect, int newValue){
         // 讀目前count的值
         int curValue = count;
         // 比較目前count值是否==期望值
         if(curValue == expect){
           // 如果是,則更新count的值
           count = newValue;
        }
         // 返回寫入前的值
         return curValue;
      }
     }

5)假如我當前的值和我取出來的那份值不一樣了,那該怎麼辦?

  • CAS一般帶有自旋,所謂自旋也就是迴圈的意思。當值不同了 ,那就從頭來進行:取值來放著--->對比--->相同的話那就更新,不同就從頭再來。

     
     class SimulatedCAS{
       volatile int count;
       // 實現count+=1
       addOne(){
         do {
           newValue = count+1; //①
        }while(count !=
           cas(count,newValue) //②
      }
       // 模擬實現CAS,僅用來幫助理解
       synchronized int cas(
         int expect, int newValue){
         // 讀目前count的值
         int curValue = count;
         // 比較目前count值是否==期望值
         if(curValue == expect){
           // 如果是,則更新count的值
           count= newValue;
        }
         // 返回寫入前的值
         return curValue;
      }
     }
  • 從上面程式碼我們也可以看出來,完全是沒有加鎖解鎖的操作的,所以CAS這種無鎖實現併發的操作效能很好。

6)我們說凡事都有兩面性,CAS他就沒任何的缺點嗎?

  • 會存在ABA問題,比如之前我取出來了一份值是A,但是在我進行對比之前,其它執行緒悄悄滴過來 ,把我的共享變數修改為了B,然後又修改成了A。雖然看到的都是A,其實這是被修改過的了。

7)兩個數值反正都是相同的,不影響我的更新,那我還在乎ABA幹嘛?

  • 如果我們只是進行數值的原子遞增之類的操作,那我們是不需要關心的。但是如果是物件呢,物件就比數值講究多了,可能 A表面都是 一樣的,但是屬性是不一樣的。

8)怎樣解決ABA問題呢?

  • 在使用 CAS 方案的時候,一定要先 check 一下。

9)前面我們使用原子類 AtomicLong 的 getAndIncrement() 方法替代了count += 1,從而實現了執行緒安全。原子類 AtomicLong 的 getAndIncrement() 方法內部就是基於 CAS 實現的,那 Java 是如何使用 CAS 來實現原子化的count += 1的?

  • Java 1.8 版本中,getAndIncrement() 方法會轉調 unsafe.getAndAddLong() 方法。這裡 this 和 valueOffset 兩個引數可以唯一確定共享變數的記憶體地址。

     final long getAndIncrement() {
       return unsafe.getAndAddLong(
         this, valueOffset, 1L);
     }

10)unsafe.getAndAddLong() 方法的底層原始碼實現是怎樣的?

  • 該方法首先會在記憶體中讀取共享變數的值

  • 之後迴圈呼叫 compareAndSwapLong() 方法來嘗試設定共享變數的值,直到成功為止。

  • compareAndSwapLong() 是一個 native 方法,只有當記憶體中共享變數的值等於 expected 時,才會將共享變數的值更新為 x,並且返回 true;否則返回 fasle。

     
     public final long getAndAddLong(
       Object o, long offset, long delta){
       long v;
       do {
         // 讀取記憶體中的值
         v = getLongVolatile(o, offset);
      } while (!compareAndSwapLong(
           o, offset, v, v + delta));
       return v;
     }
     //原子性地將變數更新為x
     //條件是記憶體中的值等於expected
     //更新成功則返回true
     native boolean compareAndSwapLong(
       Object o, long offset,
       long expected,
       long x);

11)SDK中提供了哪些原子類給我們使用?

 

 

使用提示:Java 提供的原子類能夠解決一些簡單的原子性問題,但是我們所有原子類的方法都是針對一個共享變數的,如果你需要解決多個變數的原子性問題,建議還是使用互斥鎖方案。原子類雖好,但使用要非常小心。