java Atomic 基本資料型別
前言
本文講解如下三個基本型別的原子幫助類。
AtomicInteger
AtomicBoolean
AtomicLong
由於三個類所提供的函式都差不多我們以AtomicLong
為例進行說明。
我們首先看下我們為何需要原子幫助類。
-
Long
型別寫操作不是原子性,會分為兩個32
位進行寫入(讀取是原子性,可以檢視網上相關文獻)。 -
++i
操作非原子性 -
多執行緒可見性問題
-
多執行緒
資料競爭
相關問題,如併發修改
其中舉例如下:
假設 業務邏輯A
和業務邏輯B
只能執行一個,但是在上訴程式碼中有可能同時執行 業務邏輯A
和業務邏輯B
。
案例二:
public class Main {
//火車票剩餘張數
static int i = 100;
public static void main(String[] args) throws InterruptedException {
Runnable r = () -> {
while (i >0) {
--i;
System.out.println("我搶到第"+i +"張火車票 執行緒"+Thread.currentThread().getName());
}
};
List<Thread> collect = IntStream.range(0, 100).mapToObj((i) -> {
Thread thread = new Thread(r);
return thread;
}).collect(Collectors.toList());
collect.forEach(Thread::start);
for (Thread thread : collect) {
thread.join();
}
System.out.println(i);
}
}
上面的例子模擬多執行緒搶火車票,但是你會發現多個執行緒會輸出相同序號的火車票(volatile
不提供原子性)。
Atomic
提供的類函式全部為原子性操作,原子性操作的理解:
int i=0;
i++;
我們仔細看下i++
這個步驟其實可用分為如下幾步驟:
1. 獲取i變數的記憶體空間
2. 設定i變數的數值
3. 將新數值放入記憶體
所以在多執行緒進行++i
或者i++
時往往會出現各種數值無法對應的情況(不考慮記憶體可見性)。而原子類提供類原子性操作,可以把上面三步合成一個彙編指令,當然這是利用CPU
提供的指令集完成的。
++i
非原子性問題圖:
上圖顯示兩個執行緒同時進行++i
的結果,我們預期i
應該為58
,可是卻為57
。
由於存在大量的輪子博主不在做過多的解釋,可參考如下文獻
i++位元組碼分析文章
綜上為了規避這些問題我們使用JDK提供的原子類幫助我們快速規避這類問題,
常用方法
我們首先預覽如下JDK
提供的公開函式
我們首先直接看一個使用案例在講解全部API
//火車票剩餘張數
static AtomicLong atomicLong = new AtomicLong(100);
public static void main(String[] args) throws InterruptedException {
Runnable r = () -> {
long andIncrement = 0;
//返回當前數值後 自身減一
while ((andIncrement = atomicLong.getAndDecrement()) > 0) {
System.out.println("我搶到第" + andIncrement + "張火車票 執行緒" + Thread.currentThread().getName());
}
};
List<Thread> collect = IntStream.range(0, 100).mapToObj((i) -> {
Thread thread = new Thread(r);
return thread;
}).collect(Collectors.toList());
//啟動所有執行緒
collect.forEach(Thread::start);
//等候所有執行緒結束
for (Thread thread : collect) {
thread.join();
}
}
上面的程式碼執行後不會出現多個執行緒搶到同一序號的火車票。
-
get()
以volatile
記憶體語義得到當前數值。(提供可見性,和happen-before
) -
set(Long)
以volatile
記憶體語義設定當前數值。(提供可見性,和happen-before
) -
lazySet(Long)
等價set(Long)
沒有實際懶設定,讀者可自行翻譯原始碼 -
getAndSet(Long)
設定給定的數值然後返回上一次數值 -
compareAndSet(long expect, long update)
著名的CAS
操作系列,如果當前值等於expect
,那麼更新數值為update
,如果更新成功返回true
,失敗返回false
-
weakCompareAndSet(long expect, long update)
完全等價
compareAndSet(long expect, long update)
,原始碼並沒有做額外的處理 -
getAndIncrement()
返回之前的數值然後自增1,可以替換i++等操作
-
返回之前的數值然後自身減少1,可以替換
--i
等操作返回之前的數值然後自增1,可以替換i++等操作
-
getAndAdd(long delta)
得到之前的數值,然後累加傳入的數值 -
incrementAndGet()
先自增再返回自增後的數值 -
decrementAndGet()
先自減在返回自減後的數值 -
addAndGet(long delta)
先加指定數值再返回加後的數值 -
getAndUpdate(LongUnaryOperator updateFunction)
原子性更新
LongUnaryOperator
介面返回的數值,直到成功才返回之前的數值,如果期間一直競爭那麼此函式會一直阻塞競爭成功。可以簡單理解一個死迴圈加上compareAndSet
函式。如下案例:
//火車票剩餘張數
static AtomicLong atomicLong = new AtomicLong(100);
public static void main(String[] args) throws InterruptedException {
long andUpdate = atomicLong.getAndUpdate(operand -> operand+1);
//返回100
System.out.println(andUpdate);
//返回101
System.out.println(atomicLong.get());
}
這個可以用在搶火車票的案例中。由於getAndUpdate
比較晦澀,所以我們檢視下原始碼:
//AtomicLong.java
public final long getAndUpdate(LongUnaryOperator updateFunction) {
long prev, next;
do {
//得到之前的數值
prev = get();
//呼叫開發傳入的介面函式,介面返回一個新數值
next = updateFunction.applyAsLong(prev);
//死迴圈一直到成功為止
} while (!compareAndSet(prev, next));
return prev;
}
-
updateAndGet(LongUnaryOperator updateFunction)
和getAndUpdate(LongUnaryOperator updateFunction)
差不多,只不過返回值是進行計算之後的數值。 -
getAndAccumulate(long x, LongBinaryOperator accumulatorFunction)
一直死迴圈到LongBinaryOperator
返回的數值被設定成功,返回值為設定之前的數值。
long andUpdate = atomicLong.getAndAccumulate(2, new LongBinaryOperator() {
@Override
public long applyAsLong(long left, long right) {
return left+right;
}
});
//返回100
System.out.println(andUpdate);
//返回102
System.out.println(atomicLong.get());
accumulateAndGet(long x, LongBinaryOperator accumulatorFunction)
和getAndAccumulate
功能一樣,區別在返回值為更新後的數值
相關文章
- Java基本資料型別Java資料型別
- Java 基本資料型別Java資料型別
- Java的基本資料型別Java資料型別
- JAVA中基本資料型別和引用資料型別Java資料型別
- Java中的基本資料型別與引用資料型別Java資料型別
- java基本資料型別佔位Java資料型別
- Java有哪些基本資料型別?Java資料型別
- Java技術分享:Java基本資料型別Java資料型別
- 深入理解 Java 基本資料型別Java資料型別
- java基礎之java的基本資料型別Java資料型別
- 基本資料型別資料型別
- java基礎之一:基本資料型別Java資料型別
- js資料型別之基本資料型別和引用資料型別JS資料型別
- 基本資料型別與字串型別資料型別字串
- JavaScript基本資料型別JavaScript資料型別
- python基本資料型別Python資料型別
- 003基本資料型別資料型別
- MySQL基本資料型別MySql資料型別
- Java-API-基本資料型別包裝類JavaAPI資料型別
- java基本資料型別與自動轉換Java資料型別
- Java中基本資料型別和包裝型別有什麼區別?Java資料型別
- Java基本資料型別和Integer快取機制Java資料型別快取
- Redis資料型別基本操作Redis資料型別
- 基本資料型別,for迴圈資料型別
- Python的基本資料型別Python資料型別
- (三)Python基本資料型別Python資料型別
- 3. 基本資料型別資料型別
- 基本資料型別之字串資料型別字串
- 基本資料型別轉化資料型別
- Python基本資料型別:布林型別(Boolean)Python資料型別Boolean
- JAVA_資料型別介紹與基本資料型別之間的運算規則Java資料型別
- Python3學習(基本資料型別-集合-字典-基本資料型別總結)Python資料型別
- Java資料型別Java資料型別
- Java的基本型別和引用型別Java型別
- 基本資料型別與API引用型別的使用資料型別API
- Python基本資料型別之浮點型Python資料型別
- Python基本資料型別之整型Python資料型別
- [譯]揭祕基本資料型別資料型別