1. 原子操作類的作用
當程序更新一個變量時,如果多個線程同時更新該變量,可能會得到期望以外的值。比如i=1, 線程A更新i+1, 同時線程B更新I+1,經過兩個線程的操作,最終變量i的值可能不是3,而是2。因為線程A、B拿到的i的值都是1,這就是線程不安全的更新操作。我們可以用synchronized來解決這樣的問題,synchronized可以保證多線程之間的同步,以保證多個線程不會同時操作變量i。
但是在JDK1.5開始,就提供了java.util.concurrent.atomic包,這個包中的原子操作類提供了更為簡單高效、線程安全的方式來更新一個變量的值。
2. 原子操作類基本分類
- 原子更新基本類型(3個)
- AtomicBoolean 原子更新布爾類型
- AtomicInteger 原子更新整型
- AtomicLong 原子更新長整型
- 原子更新數組(3個)
- AtomicIntegerArray 原子更新整形數組中的元素
- AtomicLongArray 原子更新長整型數組中的元素
- AtomicReferenceArray 原子更新引用類型數組中的元素
- 原子更新引用類型(3個)
- AtomicReference 原子更新引用類型
- AtomicReferenceFieldUpdater 原子更新引用類型中的字段
- AtomicMarkableReference 原子更新帶有標記位的引用類型
- 原子更新字段類(3個)
- AtomicIntegerFieldUpdater 原子更新整形字段
- AtomicLongFieldUpdater 原子更新長整型字段
- AtomicStampedReference 原子更新帶有版本號的引用類型
3. CAS方式實現原子操作基本原理
JVM中CAS操作主要是利用了處理器提供的CMPXCHG執行實現。基本的思路就是利用循環進行CAS操作,直到成功為止。CAS主要涉及到三個操作數,內存中的值(V)、舊的預期值(A)、需要修改的新值(B),當且僅當V==A時,才會將V值修改為B值,否則什么都不做,并且通過一個布爾值返回結果。偽代碼如下:
//偽代碼
boolean compareAndSwap(V,A,B){
for(;;){
if(V==A)
V=B;//替換舊值
}
}
4. CAS方式產生的問題(3個)
- ABA問題: CAS操作時,檢查值有沒有變化,如果沒有變化則更新,但是如果一個值原來是A,中間變成了B,然后又變為A,CAS進行檢查時,就會發現它的值沒有變化,但是實際上卻已經變化了。解決ABA問題,可以在變量前加一個版本號,變量更新時,版本號就加1.
- 循環時間長,開銷大:CAS采用的是自循的方式進行檢查,如果長時間不成功,那么就會給CPU帶來非常大的開銷。
- 只能保證一個共享變量的原子操作:當對一個共享變量進行原子操作時,我們可以采用CAS的方式進行更新,但是如果對多個共享變量進行操作時,CAS就無法保證操作的原子性,那么這個時候就需要用鎖來實現。
5. 原子操作類中主要的方法
- boolean compareAndSet(int expect, int update) ;如果輸入的值等于預期值,那么以原子的方式將該值設為輸入的值。
- int addAndGet(int delta);以原子的方式將輸入的數值與實例中的值相加,并返回更新之后的值
- int getAndAdd(int delta); 以原子的方式將輸入的數值與實例中的值相加,并返回舊值
- int getAndSet(int newValue);以原子方式設置為newValue的值,并返回舊值
通過閱讀源碼,可以發現CAS操作都是使用Unsafe類下的方法進行操作,而Unsafe類只提供了三種CAS方法:
- compareAndSwapObject(this, valueOffset, expect, update);
- compareAndSwapLong(this, valueOffset, expect, update);
- compareAndSwapInt(this, valueOffset, expect, update);
所以,對于其他類型的原子操作,都是進行類型轉換,將其類型轉換為這三種類型,然后進行原子操作。如Boolean型的,先轉成整整,然后在使用compareAndSwapInt進行操作;所以像char/float/double/short...等都可以按照這種思路實現。