這個包里面提供了一組原子變量類。其基本的特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具有排他性,即當某個線程進入方法,執行其中的指令時,不會被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執行完成,才由JVM從等待隊列中選擇一個另一個線程進入,這只是一種邏輯上的理解。實際上是借助硬件的相關指令來實現的,不會阻塞線程(或者說只是在硬件級別上阻塞了)??梢詫緮祿?、數組中的基本數據、對類中的基本數據進行操作。原子變量類相當于一種泛化的volatile變量,能夠支持原子的和有條件的讀-改-寫操作。
java.util.concurrent.atomic中的類可以分成4組:
標量類(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
數組類:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
更新器類:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
復合變量類:AtomicMarkableReference,AtomicStampedReference
第一組AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference這四種基本類型用來處理布爾,整數,長整數,對象四種數據,其內部實現不是簡單的使用synchronized,而是一個更為高效的方式CAS (compare and swap) + volatile和native方法,從而避免了synchronized的高開銷,執行效率大為提升。如AtomicInteger的實現片斷為:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private volatile int value;
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
構造函數(兩個構造函數)
默認的構造函數:初始化的數據分別是false,0,0,null
帶參構造函數:參數為初始化的數據
set( )和get( )方法:可以原子地設定和獲取atomic的數據。類似于volatile,保證數據會在主存中設置或讀取
void set()和void lazySet():set設置為給定值,直接修改原始值;lazySet延時設置變量值,這個等價于set()方法,但是由于字段是volatile類型的,因此次字段的修改會比普通字段(非volatile字段)有稍微的性能延時(盡管可以忽略),所以如果不是想立即讀取設置的新值,允許在“后臺”修改值,那么此方法就很有用。
getAndSet( )方法
原子的將變量設定為新數據,同時返回先前的舊數據
其本質是get( )操作,然后做set( )操作。盡管這2個操作都是atomic,但是他們合并在一起的時候,就不是atomic。在Java的源程序的級別上,如果不依賴synchronized的機制來完成這個工作,是不可能的。只有依靠native方法才可以。
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
compareAndSet( ) 和weakCompareAndSet( )方法
這 兩個方法都是conditional modifier方法。這2個方法接受2個參數,一個是期望數據(expected),一個是新數據(new);如果atomic里面的數據和期望數據一 致,則將新數據設定給atomic的數據,返回true,表明成功;否則就不設定,并返回false。JSR規范中說:以原子方式讀取和有條件地寫入變量但不 創建任何 happen-before 排序,因此不提供與除 weakCompareAndSet 目標外任何變量以前或后續讀取或寫入操作有關的任何保證。大意就是說調用weakCompareAndSet時并不能保證不存在happen- before的發生(也就是可能存在指令重排序導致此操作失?。5菑腏ava源碼來看,其實此方法并沒有實現JSR規范的要求,最后效果和 compareAndSet是等效的,都調用了unsafe.compareAndSwapInt()完成操作。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
對于 AtomicInteger、AtomicLong還提供了一些特別的方法。
getAndIncrement( ):以原子方式將當前值加 1,相當于線程安全的i++操作。
incrementAndGet( ):以原子方式將當前值加 1, 相當于線程安全的++i操作。
getAndDecrement( ):以原子方式將當前值減 1, 相當于線程安全的i--操作。
decrementAndGet ( ):以原子方式將當前值減 1,相當于線程安全的--i操作。
addAndGet( ): 以原子方式將給定值與當前值相加, 實際上就是等于線程安全的i =i+delta操作。
getAndAdd( ):以原子方式將給定值與當前值相加, 相當于線程安全的t=i;i+=delta;return t;操作。
以實現一些加法,減法原子操作。(注意 --i、++i不是原子操作,其中包含有3個操作步驟:第一步,讀取i;第二步,加1或減1;第三步:寫回內存)
package thread;
import java.util.concurrent.atomic.AtomicReference;
public class ConcurrentStack<T> {
private AtomicReference<Node<T>> stacks = new AtomicReference<Node<T>>();
public T push(T e) {
Node<T> oldNode, newNode;
for (;;) { // 這里的處理非常的特別,也是必須如此的。
oldNode = stacks.get();
newNode = new Node<T>(e, oldNode);
if (stacks.compareAndSet(oldNode, newNode)) {
return e;
}
}
}
public T pop() {
Node<T> oldNode, newNode;
for (;;) {
oldNode = stacks.get();
newNode = oldNode.next;
if (stacks.compareAndSet(oldNode, newNode)) {
return oldNode.object;
}
}
}
private static final class Node<T> {
private T object;
private Node<T> next;
private Node(T object, Node<T> next) {
this.object = object;
this.next = next;
}
}
}
雖然原子的標量類擴展了Number類,但并沒有擴展一些基本類型的包裝類,如Integer或Long,事實上他們也不能擴展:基本類型的包裝類是不可以修改的,而原子變量類是可以修改的。在原子變量類中沒有重新定義hashCode或equals方法,每個實例都是不同的,他們也不宜用做基于散列容器中的鍵值。
第二組AtomicIntegerArray,AtomicLongArray還有AtomicReferenceArray類進一步擴展了原子操作,對這些類型的數組提供了支持。這些類在為其數組元素提供 volatile
訪問語義方面也引人注目,這對于普通數組來說是不受支持的。
他們內部并不是像AtomicInteger一樣維持一個valatile變量,而是全部由native方法實現,如下AtomicIntegerArray的實現片斷:
Java代碼(http://upload-images.jianshu.io/upload_images/4417463-d8f3316d729d9df5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
rivate static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int scale = unsafe.arrayIndexScale(int[].class);
private final int[] array;
public final int get(int i) {
return unsafe.getIntVolatile(array, rawIndex(i));
}
public final void set(int i, int newValue) {
unsafe.putIntVolatile(array, rawIndex(i), newValue);
}
第三組AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater基于反射的實用工具,可以對指定類的指定 volatile
字段進行原子更新。API非常簡單,但是也是有一些約束:
(1)字段必須是volatile類型的
(2)字段的描述類型(修飾符public/protected/default/private)是與調用者與操作對象字段的關系一致。也就是說 調用者能夠直接操作對象字段,那么就可以反射進行原子操作。但是對于父類的字段,子類是不能直接操作的,盡管子類可以訪問父類的字段。
(3)只能是實例變量,不能是類變量,也就是說不能加static關鍵字。
(4)只能是可修改變量,不能使final變量,因為final的語義就是不可修改。實際上final的語義和volatile是有沖突的,這兩個關鍵字不能同時存在。
(5)對于****AtomicIntegerFieldUpdater** 和AtomicLongFieldUpdater** 只能修改int/long類型的字段,不能修改其包裝類型(Integer/Long)。如果要修改包裝類型就需要使用AtomicReferenceFieldUpdater 。
netty5.0中類ChannelOutboundBuffer統計發送的字節總數,由于使用volatile變量已經不能滿足,所以使用****AtomicIntegerFieldUpdater** **來實現的,看下面代碼:
Java代碼(http://upload-images.jianshu.io/upload_images/4417463-902b18f9d1746cb2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
/定義
private static final AtomicLongFieldUpdater<ChannelOutboundBuffer> TOTAL_PENDING_SIZE_UPDATER =
AtomicLongFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "totalPendingSize");
private volatile long totalPendingSize;
//使用
long oldValue = totalPendingSize;
long newWriteBufferSize = oldValue + size;
while (!TOTAL_PENDING_SIZE_UPDATER.compareAndSet(this, oldValue, newWriteBufferSize)) {
oldValue = totalPendingSize;
newWriteBufferSize = oldValue + size;
}