count++
並不是原子操做。由於count++
須要通過讀取-修改-寫入
三個步驟。count++
並不是原子操做。由於count++
須要通過讀取-修改-寫入
三個步驟。能夠這樣作:java
public synchronized void increase() { count++; }
CAS有3個操做數:算法
- 內存值V
- 舊的預期值A
- 要修改的新值B
咱們能夠發現CAS有兩種狀況:數組
- 若是內存值V和咱們的預期值A相等,則將內存值修改成B,操做成功!
若是內存值V和咱們的預期值A不相等,通常也有兩種狀況:多線程
- 重試(自旋)
- 什麼都不作
理解CAS的核心就是:併發
CAS是原子性的,雖然你可能看到比較後再修改(compare and swap)以爲會有兩個操做,但終究是原子性的!
java.util.concurrent.atomic
包下,整體來看有這麼多個
基本類型:高併發
- AtomicBoolean:布爾型
- AtomicInteger:整型
- AtomicLong:長整型
數組:性能
- AtomicIntegerArray:數組裏的整型
- AtomicLongArray:數組裏的長整型
- AtomicReferenceArray:數組裏的引用類型
引用類型:atom
- AtomicReference:引用類型
- AtomicStampedReference:帶有版本號的引用類型
- AtomicMarkableReference:帶有標記位的引用類型
對象的屬性線程
- AtomicIntegerFieldUpdater:對象的屬性是整型
- AtomicLongFieldUpdater:對象的屬性是長整型
- AtomicReferenceFieldUpdater:對象的屬性是引用類型
JDK8新增DoubleAccumulator、LongAccumulator、DoubleAdder、LongAddercode
- 是對AtomicLong等類的改進。好比LongAccumulator與LongAdder在高併發環境下比AtomicLong更高效。
Unsafe裏邊有幾個咱們喜歡的方法(CAS):
// 第一和第二個參數表明對象的實例以及地址,第三個參數表明指望值,第四個參數表明更新值 public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
class Count{ // 共享變量(使用AtomicInteger來替代Synchronized鎖) private AtomicInteger count = new AtomicInteger(0); public Integer getCount() { return count.get(); } public void increase() { count.incrementAndGet(); } }
下面的操做均可以正常執行完的,這樣會發生什麼問題呢??線程C沒法得知線程A和線程B修改過的count值,這樣是有風險的。
- 如今我有一個變量
count=10
,如今有三個線程,分別爲A、B、C- 線程A和線程C同時讀到count變量,因此線程A和線程C的內存值和預期值都爲10
- 此時線程A使用CAS將count值修改爲100
- 修改完後,就在這時,線程B進來了,讀取獲得count的值爲100(內存值和預期值都是100),將count值修改爲10
- 線程C拿到執行權,發現內存值是10,預期值也是10,將count值修改爲11
要解決ABA的問題,咱們可使用JDK給咱們提供的AtomicStampedReference和AtomicMarkableReference類。簡單來講就是在給爲這個對象提供了一個版本,而且這個版本若是被修改了,是自動更新的。
原理大概就是:維護了一個Pair對象,Pair對象存儲咱們的對象引用和一個stamp值。每次CAS比較的是兩個Pair對象
而LongAdder能夠歸納成這樣:內部核心數據value分離成一個數組(Cell),每一個線程訪問時,經過哈希等算法映射到其中一個數字進行計數,而最終的計數結果,則爲這個數組的求和累加。