所謂的原子性,就是在執行過程中不會被線程調度機制打斷的操作,這種操作從開始就一直運行到結束,中間不存在任何上下文切換。
還是以上篇講到的x++操作為例。這是一個典型的‘讀改寫’的操作,在多線程的情況下,必須需要硬件的支持來保證‘讀改寫’的原子性,底層原理可以簡單理解,通過鎖總線的方式來實現。不過這里咱們不說硬件,咱們先研究下Java是如何原子性實現++操作的。
在Java中,如果要實現一個在多線程下正常工作的累加計數器,首先想到的就是並發包里的AtomicXXX類,如一下例子代碼:
public class TestAtomic { private static AtomicInteger couter = new AtomicInteger(0); public static void main(String[] args)throws Exception { Thread t1 = new Thread(new Runnable() { public void run() { for(int i=0;i<1000;i++) incr(); } }); Thread t2 = new Thread(new Runnable() { public void run() { for(int i=0;i<1000;i++) incr(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(couter.get()); } public static void incr(){ couter.incrementAndGet(); } }
這里我們通過AtomicInteger實現累加器,兩個線程各執行了一千次++操作,最后正常輸出結果2000。
通過分析AtomicInteger的源碼,我們可以發現,其內部用來保存具體數值的變量是這么定義的:
private volatile int value;
它通過volatile來實現了value在多線程之間的可見性,即線程A改變了value的值,線程B讀取value時讀到的是被修改后的值。
但是之前也說到了,volatile修飾的變量,僅通過++操作是無法實現原子性的,原因上篇說了這里就不多說了。
再來看看如果實現多線程間的原子性++操作,進入AtomicInteger的incrementAndGet方法,他通過調用Unsafe的getAndAddInt方法來實現:
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
Unsafe是Java提供用來訪問系統底層的工具類,它大致有這幾個能力:
①直接分隊釋放堆外內存。Java的直接內存就是通過這個來實現。
②線程的掛起和恢復。后邊咱們要說的LockSupport就是通過這個實現。
③CAS操作。即Compare And Swap,簡單地說就是比較並交換。在保證‘讀改寫’一致性上極其有用。它在寫操作時會先比較當前內存里的值是否和改之前讀的值是否一致,如果一致則修改成功,不一致則修改失敗。
Unsafe在CAS操作一個變量時,用到了這個變量在類中的偏移位置。如AtomicInteger操作value變量時通過如下代碼先得到valueOffset:
static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
進入到Unsafe的getAndAddInt方法:
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; }
這里不斷讀取value變量的值,然后通過compareAndSwapInt操作,即CAS操作,將修改后的值寫回去,直到修改成功退出循環。
說到這里應該把AtomicInteger實現原子性++的操作說清楚了。比較簡單,總結起來就兩點:
①通過volatile實現變量value的變更對線程可見
②通過Unsafe的CAS操作,避免了一個線程的修改覆蓋另一個線程的修改,從而實現結果上的一致性。
這里我們不妨再看看Unsafe的comareAndSwapInt方法的實現,這個方法定義如下:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
這是一個用native修飾的本地方法,通過openjdk的源碼可以找到其本地實現代碼:
這里可以看到,它是先計算出了要修改的變量地址,然后調用Atomic的cmpxchg方法實現cas操作。我們繼續跟蹤cmpxchg方法:
這是x86平台下的源碼實現,可以看到它用了cmpxchgl匯編指令。也就是說,原子性操作是要硬件層面的支持。
------------------------------------------------------------
有興趣的初學者,可以加入我的圈子一起學習。
我正在「JAVA互聯網技術」和朋友們討論有趣的話題,你一起來吧?
