對Java原子類AtomicInteger實現原理的一點總結
java原子類不多,包路徑位於:java.util.concurrent.atomic,大致有如下的類:
java.util.concurrent.atomic.AtomicBoolean java.util.concurrent.atomic.AtomicInteger java.util.concurrent.atomic.AtomicIntegerArray java.util.concurrent.atomic.AtomicIntegerFieldUpdater java.util.concurrent.atomic.AtomicLong java.util.concurrent.atomic.AtomicLongArray java.util.concurrent.atomic.AtomicLongFieldUpdater java.util.concurrent.atomic.AtomicMarkableReference java.util.concurrent.atomic.AtomicReference java.util.concurrent.atomic.AtomicReferenceArray java.util.concurrent.atomic.AtomicReferenceFieldUpdater java.util.concurrent.atomic.AtomicStampedReference java.util.concurrent.atomic.DoubleAccumulator java.util.concurrent.atomic.DoubleAdder java.util.concurrent.atomic.LongAccumulator java.util.concurrent.atomic.LongAdder
普通的自增減(value++或者value--)操作為非原子操作,但是借助原子類包裝的自增減操作的保證了原子性。
測試代碼:
package com.demo; import java.util.concurrent.atomic.AtomicInteger;public class TestAtomicInteger { public static AtomicInteger value = new AtomicInteger(0);//原子類實例 // public static int value = 0; public static void main(String[] args) {for(int i=0;i<100;i++){ new Thread(){ public void run() { for (int j = 0; j < 100; j++) { value.incrementAndGet(); // value++; } }; }.start(); }while(Thread.activeCount()>1) //保證前面的線程都執行完 Thread.yield(); System.out.println(value); } }
這是一段經典的多線程訪問共享變量的實現線程安全的例子。
如果采用註釋的兩處代碼public static int value = 0;和value++;替換相應的代碼,則會出現線程安全的問題,即使將value變量用volatile修飾保證其可見性,但是由於value++本身非原子性,仍然是線程不安全的。
重點是探索一下保證原子性操作的實現過程。
首先AtomicInteger類引入了Unsafe類,該類的路徑為sun.misc.Unsafe。實際上,上面的大部分原子類都import了該類。
在AtomicInteger類內部,通過一個Unsafe類型的靜態不可變的變量unsafe來引用Unsafe的實例。
private static final Unsafe unsafe = Unsafe.getUnsafe();
然後,AtomicInteger類用value保存自身的數值,並用get()方法對外提供。
private volatile int value; public AtomicInteger(int initialValue) { value = initialValue; } ... ... /** * Gets the current value. * * @return the current value */ public final int get() { return value; }
有了如上前提,繼續往下
AtomicInteger類的incrementAndGet()方法源碼如下:
/** * Atomically increments by one the current value. * * @return the updated value */ public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
current保存當前值,這個值在後面會作為一個是否有其他線程改變value值的依據。如果沒有其他線程更改value值,那麽期望上前後兩個時間點獲取的value值應該保持不變。next保存自增1後的值,這個值是可能被更新到value的值,如果compareAndSet(current, next)返回真,自增成功。如果返回為false,表示設置不成功,可能是其他線程更改了共享變量,導致current失效,此時再次發起一輪循環。。
compareAndSet()的源碼如下:
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
直接調用了unsafe對象的compareAndSwapInt()方法,再一次追蹤該方法:
該方法位於sun.misc.Unsafe類中:
/** * Atomically update Java variable to <tt>x</tt> if it is currently * holding <tt>expected</tt>. * @return <tt>true</tt> if successful */ public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
發現該方法是native方法。
而這些諸如compareAndSwapInt(),compareAndSwapLong(),compareAndSwapObject()等的方法由虛擬機內部對其做了特殊的處理,即時編譯出來的結果就是一條平臺相關的處理器CAS(Compare-and-Swap)指令,該CAS指令甚至無法通過javap解析字節碼體現出來。
可以看出,原子類實現的自增操作可以保證原子性的根本原因在於硬件(處理器)的相關指令支持。將語義上需要多步操作的行為通過一條指令來完成,CAS指令可以達到這個目的。
CAS指令需要三個操作數:內存位置,舊預期值和新值。CAS要求,當且僅當當前內存位置處的值等於舊預期值時,就用新值更新當前內存位置處的值,否則它就不執行更新操作,整個過程正好映射了比較-交換(或者說比較-更新)的概念,同時處理過程是一個原子操作。
如果要進行一個參數對應,CAS指令需要的三個操作數:(內存位置,舊預期值和新值)可以分別對應compareAndSwapInt()方法的後三個參數:(long offset,int expected,int x)。此處的expected也即是current的值,x也即是next的值。
當然CAS指令的原子操作還存在一個“ABA”問題,大意即使講,在某個線程準備進行檢測時,如果其間其他線程將一個共享變量的值進行了多次更改後又回到了初始的值,此時該線程通過CAS檢測會認為該共享變量未發生更改,這與實際情況不符合。
通過原子類實現線程安全是非阻塞的(對比於synchronized關鍵字)。其基本的思想是基於沖突檢測與循環重試。具體講就是,需要對共享數據修改時,不加鎖先進行目標操作,如果發現有其他線程對同一份共享數據做修改(對應於檢測到當前內存位置處的值與舊預期值不等),則放棄本次修改,重寫循環再次檢測並嘗試修改,直到成功為止。
synchronized關鍵字的時間體現了悲觀鎖的策略思想,而原子類的實現則體現見了樂觀鎖的思想。
題外話:
上文提到的Unsafe類是不能被用戶源程序直接加載和實例化的,因為其構造器被限定為Unsafe類私有,Unsafe只提供getUnsafe()接口間接的對外提供Unsafe的實例,但即使是這樣,getUnsafe()方法對調用者要求任然頗為嚴苛:
@CallerSensitive public static Unsafe getUnsafe() { Class<?> caller = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe; }
而位於sun.misc.VM類的isSystemDomainLoader(loader)方法只有在參數loader為啟動加載器時,才返回true。
/** * Returns true if the given class loader is in the system domain * in which all permissions are granted. */ public static boolean isSystemDomainLoader(ClassLoader loader) { return loader == null; }
結合上述兩個方法可知,通常我們用戶程序的加載器為應用程序加載器,直接調用Unsafe是會拋異常的。
完結~~~
轉載請註明原文地址:http://www.cnblogs.com/qcblog/p/7750388.html
參考資料:
1、深入理解Java虛擬機:JVM高級特性與最佳實踐
對Java原子類AtomicInteger實現原理的一點總結