面試官:說說Java 原子類

yes的練級手冊發表於2019-04-19

面試官:說說Java 原子類

一般情況下如果我們想避免原子性問題的時都會選擇加鎖,但是我們都知道加鎖和解鎖是有消耗的。並且只要有加鎖、解鎖就會伴隨著執行緒阻塞、執行緒的喚醒,這樣執行緒的切換也是消耗效能的。

從JDK1.5起就提供了原子類,能無鎖的避免原子性問題,所以在簡單的情況下,而且是隻有就競爭一個共享變數的情況下,可以使用Java原子類,如果是多個共享變數的話基本上只能加鎖了,原子類就不太好使了! Java原子類可以分為五大類:原子更新基本型別、原子更新陣列、原子更新引用型別、原子更新屬性(類的欄位)、原子累加器。 它們是怎麼做到沒加鎖實現原子性的呢?沒什麼大祕密就是硬體的支援!我們知道CPU指令肯定是原子性的,為了解決併發的問題CPU提供了一條指令——CAS(compare and swap 即比較並交換)。

CAS 指令包含 3 個引數:共享變數的記憶體地址 A、用於比較的值 B 和共享變數的新值 C。只有當記憶體中地址A的值等於B,才能把地址A的值變成C。

也就是隻有預期值B等於記憶體地址A中的值時,表明共享變數沒有被其他執行緒修改過,所以才允許更新新值,這樣就保證了原子性!

CAS還會有ABA問題,就是當你比較的時候,可能你的值被一個執行緒改了之後,另一個執行緒又改了回來,然後你比較的時候發現和預期值一樣,其實是被改過的。就類似你走在路上,你手機放口袋裡,小偷偷走了你的手機,但是一抬頭看到了頭上的監控,又默默的放回你的口袋。你根本不知道發生了這件事,然後悠然的拿起手機,刷了波朋友圈,又默默的塞進口袋。。。

大部分情況下不需要關心這個,例如基本型別等,但是原子更新引用型別,因為可能比較的時候執行緒但是裡面的某個屬性已經變了。可以新增一個版本號來避免這個問題。

通常利用CAS來解決併發問題都通過自旋手段,這裡的自旋的其實迴圈嘗試,再說白一點就是while迴圈。舉個例子來看一下AtomicInteger原始碼,宣告瞭 private volatile int value; 通過volatile 關鍵字保證可見性。

public final int getAndIncrement() {
   return unsafe.getAndAddInt(this, valueOffset, 1);
}

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;
}

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
複製程式碼

原子類都是呼叫sun.misc.Unsafe來的實現的,就比如上面的程式碼就是呼叫unsafe.getAndAddInt()。 do while就是自旋操作了,compareAndSwapInt是native方法。

所有的原子類實現基本上都是這個思路!講白了就是呼叫硬體提供CAS這種操作,Java併發包幫我們封裝了一下,使得我們更容易的呼叫!

原子更新基本型別

相關實現類:AtomicBoolean、AtomicInteger、AtomicLong。主要就是用來基本型別的操作

原子更新陣列型別

相關實現類:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray。可以原子化地更新陣列裡面的每一個元素,和原子更新基本型別差別就是基本上方法都多了個陣列的下標。

原子更新引用型別

相關實現類:AtomicReference、AtomicStampedReference 、AtomicMarkableReference。這個就要重點關注ABA,但是Java已經關注到了所以AtomicStampedReference 和 AtomicMarkableReference 就能避免ABA問題了,就是用了版本號!

原子更新欄位型別

相關實現類:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater 它們可以原子化地更新物件的屬性,注意更新類的欄位(屬性)必須使用public volatile修飾符,這樣才能保證可見性。

原子累加器

相關實現類:LongAccumulator、LongAdder、DoubleAccumulator、DoubleAdder。這幾個類只能用來進行累加操作,現對於原子更新基本型別它們的效能更好些,所以如果只有累加操作可以用這幾個類!


如果錯誤歡迎指正!個人公眾號:yes的練級攻略

相關文章