1、Atomic中存在Atmomicxxx的類,都是通過CAS來實現原子性的。
對於平時適用count++問題,count++並不是線程安全的,所以在多線程情況下,適用count++會出現得到的值並不是我們期望的值。
問題如下:
所以爲了解決此類問題我們需要用到Atomic,例如我們可以適用AtomicInteger來代替count++操作,保證線程安全。
例子如下:
/**
* @author v_vllchen
*/
public class AtomicExample {
private static final Logger LOGGER = LoggerFactory.getLogger(AtomicExample.class);
// 請求總數
public static int clientTotal = 5000;
// 同時併發執行的線程數
public static int threadTotal = 200;
public static int count2 = 0;
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count:{}"+count.get());
LOGGER.info("count:{}", count.get());
}
private static void add() {
count.incrementAndGet();
}
}
代碼來自https://blog.csdn.net/qq_34871626/article/details/81411815
在該代碼中我們可以看到我們請求的總數是5000,併發執行的線程數爲200,通過累加對AtomicInteger進行累加可以看到最終的結果是5000;
以上我們可以看到適用Atomic的確解決了count++的線程安全問題,但是底層如何通過CAS來實現的這樣一個過程呢,還是要通過源碼來解釋
1. 在add()方法中有一個incrementAndGet方法,我們可以進入這個源碼內部
看到它調用了unsafe類下面的getAndAddInt方法,這個方法中有三個參數,第一個參數是對象本身、第二個參數是valueOffset用來記錄當前value在內存中的編譯地址,第三個參數爲常量
2. 繼續深入查看unsafe中的getAndAddInt方法,
從第一個步中我們知道了var1是對象本身,var2是value在內存中的編譯地址,var4是一個常數。 該方法中有兩個方法:
1、getIntVolatile(),該方法通過var1以及var2來找到內存中的值,該方法是一個native方法,並不是Java實現的。
2、compareAndSwapInt(),在方法中通過while語句來實現compareAndSwapInt()方法,該方法是用來比較var1與getIntVolatile()底層返回的value,如果相同則把var5更新爲var5+var4,如果不相同,則循環通過getIntVolatile()獲取底層的value值,直到當前的var1與底層的返回的value相同才做更新。
compareAndSwapInt()方法的核心就是通常所說的CAS。
以上就是Atomic的實現思想
但是對於CAS來說會遇到一個經常性的問題就是ABA問題(線程1將值A改成B,接着又改回了A,其它線程通過與值做比較時發現值沒有改變,則直接進行更改,實際上值已經被更改了)。
所以對於這種問題,在Atomic中通過AtomicStampReference類來解決ABA問題。ABA問題的解決辦法就是在線程每次去更新的時候將版本號加一,這樣其它線程通過版本號檢測該值是否被更新過。
public class AtomicStampedReference<V> {
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
... 此處省略多個方法 ....
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
}