多線程使用會導致不安全,其中原子性就是一個不可以破壞的。原子性指的是一條指令不可以再被分割成多個操作,而是一起完成也就是要麼全部執行成功要麼全部執行失敗。我們常見的不滿足原子性的操作就是對共享變量進行 i++。通常我們使用synchronized 關鍵字來解決這個問題,在 JDK 1.5 中開始提供了
java.util.concurrent.atomic
包,這個包中的原子操作類提供了一種用法簡單,性能高效的方式。如下圖:
一、原子更新基本類型
原子更新基本類型的三個類:
- AtomicInteger
- AtomicBoolean
- AtomicLong
這三個類基本實現都是差不多的,我們以AtomicInteger
爲例來看看它的一些常用方法:
方法 | 解釋 |
---|---|
int addAndGet(int delta) |
將給定的值原子地與當前值相加,返回結果 |
boolean compareAndSet(int expect, int update) |
如果輸入的值等於期望值,則將該值原子設置爲給定的更新值。 |
int getAndIncrement() |
原子上給當前值 +1 |
int getAndDecrement() |
原子上給當前值 - 1 |
int getAndSet(int newValue) |
將原子設置爲給定值並返回舊值。 |
這裏我們選擇的來看看int getAndIncrement()
是怎麼實現的:
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;
}
實際上,就是調用了getAndAddInt
方法,得到舊值和新值後調用compareAndSwapInt
進行CAS直到設置成功,如果發現輸入值不是期望值的時候就設置失敗,然後進行循環,直到設值成功。
我們發現,只提供了這三種基本類型的更新,那麼其它類型呢?float、char等等,又是怎麼實現的呢?
Atomic包基本都是使用Unsafe實現的。
我們發現提供了三種方式,再看看AtomicBoolean源碼:
這裏我們發現實際上是把Boolean先轉化成整型,接着再使用compareAndSwapInt
方法進行CAS,所以對於其它類型也可以通過這中方式來實現。
二、原子更新數組
原子更新數組的三個類:
- AtomicIntegerArray —— 原子更新整型數組中的元素
- AtomicLongArray —— 原子更新長整型數組中的元素
- AtomicReferenceArray —— 原子更新引用類型數組的元素
接着我們來看看AtomicIntegerArray
方法 | 解釋 |
---|---|
int addAndGet(int i, int delta) |
以原子的方式將輸入值與數組索引i的元素相加 |
boolean compareAndSet(int i, int expect, int update) |
如果輸入的值等於期望值,以原子的方式將數組i位置的元素設置爲新值 |
三、原子更新引用
原子更新引用類型的三個類:
- AtomicReference —— 原子更新引用類型
- AtomicReferenceFieldUpdater —— 原子更新引用類型的字段
- AtomicMarkableReference —— 原子更新帶有標誌位的引用類型
以AtomicReference
來看看它的方法:
方法 | 解釋 |
---|---|
V getAndSet(V newValue) |
將原子設置爲給定值並返回舊值。 |
boolean compareAndSet(int expect, int update) |
如果輸入的值等於期望值,以原子的方式將元素設置爲新值 |
四、原子更新屬性
如果要原子的更新某個類裏的某個字段是,就需要使用原子更新字段類,有以下三種:
- AtomicIntegerFieldUpdater —— 原子更新整型的字段
- AtomicLongFieldUpdater —— 原子更新長整型的字段
- AtomicStampedReference —— 原子跟新帶有版本號的引用類型。
其中最後一種帶有版本號的更新可以用來解決使用CAS進行原子更新可能出現的ABA問題。
想要原子更新字段需要兩步,第一步:因爲原子跟新字段類都是抽象類,每次使用時必須使用靜態方法new Updater()
創建一個更新器,並且需要設置想要更新的類和屬性。第二步就是更新的字段必須使用public volatile
來修飾。
方法 | 解釋 |
---|---|
abstract boolean compareAndSet(T obj, int expect, int update) |
如果當前值爲預期值,則將由此更新程序管理的給定對象的字段原子設置爲給定的更新值。 |
int getAndSet(T obj, int newValue) |
將由此更新程序管理的給定對象的字段原子設置爲給定值,並返回舊值。 |
int getAndAdd(T obj, int delta) |
將給定值原子地添加到由此更新程序管理的給定對象的字段的當前值。 |
其實可以看出,不管是哪種,基本的提供的方法都是相似的,所以掌握一個類,其它的也就觸類旁通了。
文章參考《Java併發編程的藝術》一書,很推薦大家看這本書。文章如果有什麼問題也歡迎大家指正,希望自己表達的能對你有所幫助,也歡迎大家點贊關注一起進步!