前言
原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型) 這個操作是不可分割的,那么我們說這個操作時原子操作。再比如:a++; 這個操作實際是a = a + 1;是可分割的,所以它不是一個原子操作(線程執行a=0這個語句時直接將數據寫入內存中;而執行a++時,會先獲取a的值,再去執行加操作,最后再將數據寫入內存)。只有簡單的讀取、賦值(而且必須是將數字賦值給某個變量,變量之間的相互賦值不是原子操作)才是原子操作。非原子操作都會存在線程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那么我們稱它具有原子性。Java從JDK1.5開始提供了java.util.concurrent.atomic包,方便程序員在多線程環境下,無鎖的進行原子操作。原子變量的底層使用了處理器提供的原子指令,但是不同的CPU架構可能提供的原子指令不一樣,也有可能需要某種形式的內部鎖,所以該方法不能絕對保證線程不被阻塞。
Atomic包介紹
在Atomic包里一共有12個類,四種原子更新方式,分別是原子更新基本類型,原子更新數組,原子更新引用和原子更新字段。Atomic包里的類基本都是使用Unsafe實現的包裝類。
原子更新基本類型
用於通過原子的方式更新基本類型,Atomic包提供了以下三個類:
- AtomicBoolean:原子更新布爾類型。
- AtomicInteger:原子更新整型。
- AtomicLong:原子更新長整型。
AtomicInteger的常用方法如下:
- AtomicInteger(int initialValue):創建一個AtomicInteger實例,初始值由參數指定。不帶參的構造方法初始值為0。
- int addAndGet(int delta) :以原子方式將輸入的數值與實例中的值(AtomicInteger里的value)相加,並返回結果,與getAndAdd(int delta)相區分,從字面意思即可區分,前者返回相加后結果,后者先返回再相加。
- boolean compareAndSet(int expect, int update) :如果當前值等於預期值,則以原子方式將該值設置為輸入的值。
- int getAndIncrement():以原子方式將當前值加1,注意:這里返回的是自增前的值。
- void lazySet(int newValue):最終會設置成newValue,使用lazySet設置值后,可能導致其他線程在之后的一小段時間內還是可以讀到舊的值。關於該方法的更多信息可以參考並發網翻譯的一篇文章《AtomicLong.lazySet是如何工作的?》
- int getAndSet(int newValue):以原子方式設置為newValue的值,並返回舊值。
AtomicInteger例子代碼如下:
1 import java.util.concurrent.atomic.AtomicInteger; 2 3 public class Demo 4 { 5 // 初始值設為1 6 static AtomicInteger atom = new AtomicInteger(1); 7 8 public static void main(String[] args) 9 { 10 System.out.println("初始值 = " + atom); 11 12 // 以原子的方式加1,注意是先返回原數值再加1 13 System.out.println("調用getAndIncrement()返回值 = " + atom.getAndIncrement()); 14 System.out.println("調用getAndIncrement()后初始值變為 = " + atom); 15 16 // 以原子的方式與指定數值相加,注意是先加再返回相加后的值 17 System.out.println("調用addAndGet()返回值 = " + atom.addAndGet(10)); 18 System.out.println("調用addAndGet()后初始值變為 = " + atom); 19 20 // 以原子的方式將當前值設為指定數值,注意是先返回原值再設為指定值 21 System.out.println("調用getAndSet()返回值 = " + atom.getAndSet(-5)); 22 System.out.println("調用getAndSet()后初始值變為 = " + atom); 23 24 // 如果當前值等於預期值(第一個參數),則以原子的方式將當前值設置為指定值,並返回true,否則返回false 25 System.out.println("調用compareAndSet()返回值 = " + atom.compareAndSet(-5, 100)); 26 System.out.println("調用compareAndSet()后初始值變為 = " + atom); 27 } 28 }
結果如下:
初始值 = 1 調用getAndIncrement()返回值 = 1 調用getAndIncrement()后初始值變為 = 2 調用addAndGet()返回值 = 12 調用addAndGet()后初始值變為 = 12 調用getAndSet()返回值 = 12 調用getAndSet()后初始值變為 = -5 調用compareAndSet()返回值 = true 調用compareAndSet()后初始值變為 = 100
那 AtomicInteger 怎么實現院子操作的呢,以 getAndIncrement() 為例,我們看看源碼是怎樣實現這個方法的
public final int getAndIncrement() { for (;;) { // 獲取AtomicInteger當前對應的long值 int current = get(); // 將current加1 int next = current + 1; // 通過CAS函數,更新current的值 if (compareAndSet(current, next)) return current; } }
getAndIncrement()首先會根據get()獲取AtomicInteger對應的int值。該值是volatile類型的變量,當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時發現此時值改變了,它會去內存中讀取新值。而普通的共享變量不能保證可見性,因為普通共享變量被修改之后,什么時候被寫入主存是不確定的,當其他線程去讀取時,此時內存中可能還是原來的舊值,因此無法保證可見性。
get()的源碼如下:
// value是AtomicInteger對應的int值 private volatile int value; // 返回AtomicInteger對應的int值 public final int get() { return value; }
getAndIncrement()接着將current加1,然后通過CAS函數(樂觀鎖),將新的值賦值給value。compareAndSet()的源碼如下:
1 public final boolean compareAndSet(intexpect, int update) { 2 return unsafe.compareAndSwapLong(this, valueOffset, expect, update); 3 }
調用 Unsafe 來實現
private static final Unsafe unsafe = Unsafe.getUnsafe();
這段代碼寫得很巧妙:
1. compareAndSet 方法首先判斷當前內存值this是否等於預期值current;
2. 如果當前值 = current,說明 AtomicInteger 類的值沒有被其他線程修改,則將內存值更新為next
3. 如果當前值 != current,說明 AtomicInteger 類的值已經被其他類修改了,這時會再次進入循環重新獲取更新后值並比較
valueOffset表示的是變量值在內存中的偏移地址,因為Unsafe就是根據內存偏移地址獲取數據的原值的。
Atomic包提供了三種基本類型的原子更新,但是Java的基本類型里還有char,float和double等。那么問題來了,如何原子的更新其他的基本類型呢?Atomic包里的類基本都是使用Unsafe實現的,它提供了硬件級別的原子操作。讓我們一起看下Unsafe的源碼,發現Unsafe只提供了三種CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源碼,發現其是先把Boolean轉換成整型,再使用compareAndSwapInt進行CAS,所以原子更新double也可以用類似的思路來實現。
參考資料: