java.util.concurrent.atomic原子操作類包

這個包里面提供了一組原子變量類。其基本的特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具有排他性,即當某個線程進入方法,執行其中的指令時,不會被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執行完成,才由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;  
        }  
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,947評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,094評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,268評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,817評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,039評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,551評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,502評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,662評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,907評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,255評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,637評論 2 370

推薦閱讀更多精彩內容

  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,311評論 11 349
  • 接著上節 mutex,本節主要介紹atomic的內容,練習代碼地址。本文參考http://www.cplusplu...
    jorion閱讀 73,735評論 1 14
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區別 13、...
    Miley_MOJIE閱讀 3,718評論 0 11
  • 四毛大叔閱讀 340評論 2 7
  • 有一年在育才中學、我們穿著校服 有一年在育才中學、我們找著機會不穿校服 有一年在育才中學、我們會一起放學,一起打掃...
    哎我去累閱讀 260評論 0 0