簡單使用AtomicInteger
首先貼出以下的代碼,簡單的使用AtomicInteger這個類來實現+1的操作。
import java.util.concurrent.atomic.AtomicInteger;
public class CASTest {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(10);
for (int i = 0; i < 10; i ++) {
new Thread(new Runnable() {
@Override
public void run() {
atomicInteger.addAndGet(1);
}
}).start();
}
System.out.println(atomicInteger);
}
}
打印的值是20,可見是線程安全的。以下逐步分析其中蘊含的CAS原理。
提供增加操作的addAndGet方法
分析其中的addAndGet方法,其源碼如下
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
發現它是調用unsafe包的方法,首先對傳給unsafe的getAnddAddInt的參數進行說明:
- this指當前的AtomicInteger對象
- valueOffset由AtmoicInteger類中的靜態代碼塊確定,指的是AtomicInteger的value屬性在內存中的偏移量
- delta即要value值要改變的大小
實際執行修改操作的getAndAddInt方法
再來看unsafe包中的getAndAddInt的方法,為cas原理的核心方法
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
這個方法返回的值其實是修改前的值,所以addAndGet方法返回的是改方法的返回值 + delta
主要有以下步驟:
- do循環中通過getIntVolatile本地方法來獲取主存中的Atomic的value值,並賦值給局部變量v
- compareAndSwapInt來同樣通過o,offset參數來獲取主存中的value值,代碼中並未體現
- 通過比較v與compareAndSwapInt方法中再次獲得的值來判斷修改是否執行,即
v = v + delta
操作 - 成功則跳出循環,返回修改前的值
- 不成功則一直循環,自旋在此處
其他方法
其中getIntVolatile與compareAndSwapInt方法簽名如下
//** Volatile version of {@link #getInt(Object, long)}
public native int getIntVolatile(Object o, long offset);
//Atomically update Java variable to x if it is currently holding expected.Returns:true if successful
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
CAS的缺點
可以看到CAS原理其中是不斷比較最初的值,和要進行修改時的值進行比較,來判斷是否有其他線程修改了此變量;如果變量被修改,則不會繼續修改變量。但是CAS是一種自旋的操作,會占用CPU的時間塊分配的。以及存在這樣一個邏輯漏洞:如果一個變量V初次讀取的時候是A值,並且在准備賦值的時候檢查到它仍然是A值,那我們就能說明它的值沒有被其他線程修改過了嗎?如果在這段期間它的值曾經被改成了B,然后又改回A,那CAS操作就會誤認為它從來沒有被修改過。這個漏洞稱為CAS操作的"ABA"問題。