深入解析Java AtomicInteger原子類型
在並發編程中,需要確保當多個線程同時訪問時,程序能夠獲得正確的結果,即實現線程安全。線程安全性定義如下:
當多個線程訪問一個類時,無論如何調度運行時環境或如何交替執行這些線程,並且主代碼中不需要額外的同步或協作,該類都可以正確地運行,因此該類是線程安全的。
線程安全需要兩件事:
- 保證線程的內存可見性
- 保證原子性
以線程不安全性為例。如果我們想要實現一個函數來對頁面訪問進行計數,那么您可能想要count+,但是這個增量操作不是線程安全的。Count++可以分為三個操作:
- 獲取變量當前值
- 給獲取的當前變量值+1
- 寫回新的值到變量
假設計數的初始值為10,當執行並發操作時,線程A和B可以同時進行1次操作,然后進行2次操作。A前進到3+1,當前值為11。注意,AB剛才獲得的當前值是10,所以在B執行3次操作之后,計數仍然是11。這個結果顯然不符合我們的要求。
因此,我們需要使用本文的主角Atomic Integer來確保線程安全。
Atomic Integer的源代碼如下:
1 package java.util.concurrent.atomic; 2 import sun.misc.Unsafe; 3 4 public class AtomicInteger extends Number implements java.io.Serializable { 5 private static final long serialVersionUID = 6214790243416807050L; 6 7 // setup to use Unsafe.compareAndSwapInt for updates 8 private static final Unsafe unsafe = Unsafe.getUnsafe(); 9 private static final long valueOffset; 10 11 static { 12 try { 13 valueOffset = unsafe.objectFieldOffset 14 (AtomicInteger.class.getDeclaredField("value")); 15 } catch (Exception ex) { throw new Error(ex); } 16 } 17 18 private volatile int value; 19 20 public AtomicInteger(int initialValue) { 21 value = initialValue; 22 } 23 24 public AtomicInteger() { 25 } 26 27 public final int get() { 28 return value; 29 } 30 31 public final void set(int newValue) { 32 value = newValue; 33 } 34 35 public final void lazySet(int newValue) { 36 unsafe.putOrderedInt(this, valueOffset, newValue); 37 } 38 39 public final int getAndSet(int newValue) { 40 for (;;) { 41 int current = get(); 42 if (compareAndSet(current, newValue)) 43 return current; 44 } 45 } 46 47 public final boolean compareAndSet(int expect, int update) { 48 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 49 } 50 51 public final boolean weakCompareAndSet(int expect, int update) { 52 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 53 } 54 55 public final int getAndIncrement() { 56 for (;;) { 57 int current = get(); 58 int next = current + 1; 59 if (compareAndSet(current, next)) 60 return current; 61 } 62 } 63 64 public final int getAndDecrement() { 65 for (;;) { 66 int current = get(); 67 int next = current - 1; 68 if (compareAndSet(current, next)) 69 return current; 70 } 71 } 72 73 public final int getAndAdd(int delta) { 74 for (;;) { 75 int current = get(); 76 int next = current + delta; 77 if (compareAndSet(current, next)) 78 return current; 79 } 80 } 81 82 public final int incrementAndGet() { 83 for (;;) { 84 int current = get(); 85 int next = current + 1; 86 if (compareAndSet(current, next)) 87 return next; 88 } 89 } 90 91 public final int decrementAndGet() { 92 for (;;) { 93 int current = get(); 94 int next = current - 1; 95 if (compareAndSet(current, next)) 96 return next; 97 } 98 } 99 100 public final int addAndGet(int delta) { 101 for (;;) { 102 int current = get(); 103 int next = current + delta; 104 if (compareAndSet(current, next)) 105 return next; 106 } 107 } 108 109 public String toString() { 110 return Integer.toString(get()); 111 } 112 113 114 public int intValue() { 115 return get(); 116 } 117 118 public long longValue() { 119 return (long)get(); 120 } 121 122 public float floatValue() { 123 return (float)get(); 124 } 125 126 public double doubleValue() { 127 return (double)get(); 128 } 129 130 }
AtomicInteger 類中定義的屬性
不安全是JDK中的一個工具類,它主要實現與平台相關的操作。以下引用自
太陽。雜項。不安全是JDK中使用的工具類。通過將Java意義上的一些“不安全”功能暴露給Java層代碼,JDK可以更多地使用Java代碼來實現與平台相關的一些功能,並且需要使用本機語言(如C或C++)來實現。這個類不應該在JDK核心類庫之外使用。
不安全的實現與本文的目標幾乎沒有關系。您只需要知道這個代碼是獲取堆內存中的值偏移量。偏移量在原子整數中非常重要。原子操作依賴於它。
Value的定義和volatile
AtomicInteger本身是一個整數,因此最重要的屬性是值。讓我們看看它是如何聲明值的。
1 private volatile int value;
我們看到值使用易失性修飾符,那么什么是易失性呢?
易失性相當於同步的弱實現,也就是說,易失性實現了類似於同步的語義而無鎖定機制。它確保以可預測的方式將易失性字段的更新傳遞給其他線程。
Volatile包含以下語義:
- Java 存儲模型不會對valatile指令的操作進行重排序:這個保證對volatile變量的操作時按照指令的出現順序執行的。
- volatile變量不會被緩存在寄存器中(只有擁有線程可見)或者其他對CPU不可見的地方,每次總是從主存中讀取volatile變量的結果。也就是說對於volatile變量的修改,其它線程總是可見的,並且不是使用自己線程棧內部的變量。也就是在happens-before法則中,對一個valatile變量的寫操作后,其后的任何讀操作理解可見此寫操作的結果。
volatile 主要特性有兩點:1.防止重排序。2. 實現內存可見性 。內存可見性的作用是當一個線程修改了共享變量時,另一個線程可以讀取到這個修改后的值。在分析AtomicInteger 源碼時,我們了解到這里就足夠了。
用CAS操作實現原子性
Atomic Integer中有很多方法,比如等價於i++的.mentAndGet()和等價於i+=n的getAndAdd()。
源碼如下:
1 public final int incrementAndGet() { 2 for (;;) { 3 int current = get(); 4 int next = current + 1; 5 if (compareAndSet(current, next)) 6 return next; 7 } 8 } 9 10 public final boolean compareAndSet(int expect, int update) { 11 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 12 }
incrementAndGet()
方法實現了自增的操作。核心實現是先獲取當前值和目標值(也就是value+1),如果compareAndSet(current, next)
返回成功則該方法返回目標值。那么compareAndSet是做什么的呢?理解這個方法我們需要引入CAS操作。
我們在大學操作系統課程中了解了獨占鎖和樂觀鎖的概念。獨占鎖意味着所有線程都需要掛起直到持有獨占鎖的線程釋放鎖;樂觀鎖假定沒有沖突可以直接操作,如果由於沖突而失敗,則重試直到操作成功。其中,CAS是用於樂觀鎖定的機制,稱為比較和交換。
Atomic Integer中的CAS操作是.eAndSet(),其功能是每次根據值Offset從內存中檢索數據,並將檢索到的值與expect進行比較。如果數據是一致的,則將內存中的值更改為更新。如果數據不一致,則意味着內存中的數據已經更新,然后將回滾(期望值無效)。
這樣,使用CAS方法保證了原子操作。Atomic Long、Atomic Boolean和Java中的其他方法的基本原理和思想與原子整數的基本原理和思想基本相同。這篇文章不會解釋太多。
你可能會問,同步關鍵字也可以實現並發操作啊,為什么不使用同步呢?
事實上,在我研究Atomic Integer源代碼之前,我認為它是通過同步的原子操作在內部實現的。后來,搜索發現,原子操作的原始同步實現會影響性能,因為Java中的同步鎖是排他鎖,雖然可以實現原子操作,但是這種實現的並發性能很差。
總結
總之,Atomic Integer主要實現線程安全的整數操作,以防止並發情況下出現異常結果。其內部實現主要依賴於JDK中易失性和不安全的類操作存儲器數據。易失性修飾符確保了內存中的其他線程能夠看到值得更改的值,即實現了內存可見性。CAS操作確保了原子整數可以安全地修改值,實現原子性。volatile和CAS操作的組合實現了線程安全。