1.概述
- Java從JDK1.5開始提供了java.util.concurrent.atomic包,方便程序員在多線程環境下,無鎖的進行原子操作。原子變量的底層使用了處理器提供的原子指令,但是不同的CPU架構可能提供的原子指令不一樣,也有可能需要某種形式的內部鎖,所以該方法不能絕對保證線程不被阻塞。
- 原子類其內部實現不是簡單的使用synchronized,而是一個更爲高效的方式CAS (compare and swap) + volatile和native方法(同步的工作更多的交給了硬件),從而避免了synchronized的高開銷,執行效率大爲提升
- 雖然基於CAS的線程安全機制很好很高效,但要說的是,並非所有線程安全都可以用這樣的方法來實現,這隻適合一些粒度比較小,型如計數器這樣的需求用起來纔有效,否則也不會有鎖的存在了
- 在Atomic包裏一共有12個類,四種原子更新方式,分別是原子更新基本類型,原子更新數組,原子更新引用和原子更新字段。Atomic包裏的類基本都是使用Unsafe實現的包裝類。
2.代碼詳解
現在我們來分析一下Atomic包中AtomicInteger的源代碼,其它類的源代碼在原理上都比較類似。
- 有參構造函數:從構造函數函數可以看出,數值存放在成員變量value中;
- 成員變量value聲明爲volatile類型,說明了多線程下的可見性,即任何一個線程的修改,在其它線程中都會被立刻看到
public AtomicInteger(int initialValue) {
value = initialValue;
}
- compareAndSet方法(value的值通過內部this和valueOffset傳遞)
- 這個方法就是最核心的CAS操作
- 接收2個參數,一個是期望數據(expected),一個是新數據(new);如果atomic裏面的數據和期望數據一致,則將新數據設定給atomic的數據,返回true,表明成功;否則就不設定,並返回false。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
- getAndSet方法,在該方法中調用了compareAndSet方法
- 本質是get( )操作,然後做set( )操作。儘管這2個操作都是atomic,但是他們合併在一起的時候,就不是atomic
- 如果在執行if(compareAndSet(current,newValue)之前其它線程更改了value的值,那麼導致value的值必定和current的值不同,compareAndSet執行失敗,只能重新獲取value的值,然後繼續比較,直到成功。
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
- i++的實現
- 一般來說自增和自減都不是原子操作,其中包含有3個操作步驟:第一步,讀取i;第二步,加1或減1;第三步,寫回內存
- 爲了實現原子性,必須在for循環中比較值value是否被修改
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
- ++i的實現
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
- 實例:使用AutomicInteger實現隨機數字生成器
puclic class AutomicRandom{
private AutomicInteger seed;
AutomicRandom(int seed){
this.seed = new AutomicInteger(seed);
}
public int nextInt(){
while(true){
int s = seed.get();
int nextSeed = calculateNext(s);
if(seed.compareAndSet(s,nextSeed)){
//對比新舊數據是否一致,一致則設置
//新的數字成功設置到原子類對象裏面
int remainder = s % n;
return remainder > 0 ? remainder : remainder + n;
}
}
}
}
3. 總結
- 在中低程度的競爭下,原子類提供更高的伸縮性;在高強度的競爭下,鎖能更好的幫助我們避免競爭。(來自《併發編程實戰》)
- 所以,我們要視情況而定,若資源競爭規模不大,控制粒度較小,使用原子類比使用鎖更好,能提高效率與性能