Java 原子變數原理及使用場景
原子變數不使用鎖或其他同步機制來保護對其值的併發訪問。所有操作都是基於CAS原子操作的。他保證了多執行緒在同一時間操作一個原子變數而不會產生資料不一致的錯誤,並且他的效能優於使用同步機制保護的普通變數,譬如說在多執行緒環境 中統計次數就可以使用原子變數。
話不多說看原始碼:
通過比較valueOffset處的記憶體的值是否為expect,是的話就更新替換成新值update,這個操作是原子性的。volatile保證了可見性,而unsafe保證了原子性
public final int get() //獲取當前的值
public final int getAndSet(int newValue)//獲取當前的值,並設定新的值
public final int getAndIncrement()//獲取當前的值,並自增
public final int getAndDecrement() //獲取當前的值,並自減
public final int getAndAdd(int delta) //獲取當前的值,並加上預期的值
在多執行緒環境中,可以很多情況可使用該關鍵字替換synchronized
舉個列子:
限制併發流量
在高併發的場景下,也可以用來限制介面的流量,超過併發的數量的閾值進行熔斷等操作,舉個列子
執行main方法可以發現,當執行緒併發數超過10個的時候,會輸出reject request
總結:
1、藉助volatile原語,保證執行緒間的資料是可見的(共享的)
2、採用了CAS操作,每次從記憶體中讀取資料然後將此資料和+1後的結果進行CAS操作,如果成功就返回結果,否則重試直到成 功為止 compareAndSet利用JNI來完成CPU指令的操作
3、ABA問題
比如說執行緒one從記憶體位置V中取出A,這時候另一個執行緒two也從記憶體中取出A,並且two進行了一些操作變成了B,然後two又將V位置的資料變成A,這時候執行緒one進行CAS操作發現記憶體中仍然是A,然後one操作成功。儘管執行緒one的CAS操作成功,但是不代表這個過程就是沒有問題的。如果連結串列的頭在變化了兩次後恢復了原值,但是不代表連結串列就沒有變化
要解決"ABA問題",我們需要增加一個版本號,在更新變數值的時候不應該只更新一個變數值,而應該更新兩個值,分別是變數值和版本號
4、synchronized的成本相對較高,需要獲取鎖物件,釋放鎖物件 使用原子變數可以避免多執行緒的優先順序倒置和死鎖情況的發生,提升在高併發處理下的效能
原始碼(jdk7):
private volatile int value;
public final int incrementAndGet() {
for (; ; ) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next)) return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}