無鎖AtomicXXX源碼分析

無鎖即無障礙的運行, 所有線程都可以到達臨界區(qū), 接近于無等待.無鎖采用CAS(compare and swap)算法來處理線程沖突, 其原理如下
CAS原理
CAS包含3個參數(shù)CAS(V,E,N).V表示要更新的變量, E表示預(yù)期值, N表示新值.僅當(dāng)V值等于E值時, 才會將V的值設(shè)為N, 如果V值和E值不同, 則說明已經(jīng)有其他線程做了更新, 則當(dāng)前線程什么都不做. 最后, CAS返回當(dāng)前V的真實值. CAS操作是抱著樂觀的態(tài)度進行的, 它總是認(rèn)為自己可以成功完成操作.

當(dāng)多個線程同時使用CAS操作一個變量時, 只有一個會勝出, 并成功更新, 其余均會失敗.失敗的線程不會被掛起,僅是被告知失敗, 并且允許再次嘗試, 當(dāng)然也允許失敗的線程放棄操作.基于這樣的原理, CAS操作即時沒有鎖,也可以發(fā)現(xiàn)其他線程對當(dāng)前線程的干擾, 并進行恰當(dāng)?shù)奶幚?
CPU指令
另外, 雖然上述步驟繁多, 實際上CAS整一個操作過程是一個原子操作, 它是由一條CPU指令完成的,從指令層保證操作可靠, 不會被多線程干擾.

無鎖與volatile
當(dāng)給變量加了volatile關(guān)鍵字, 表示該變量對所有線程可見, 但不保證原子性.

AtomicInteger

// 取得當(dāng)前值
public final int get() 
// 設(shè)置當(dāng)前值
public final void set(int newValue)
// 設(shè)置新值,并返回舊值
public final int getAndSet(int newValue)
// 如果當(dāng)前值為expect嫡秕,則設(shè)置為u
public final boolean compareAndSet(int expect, int u)
// 當(dāng)前值加1,返回舊值
public final int getAndIncrement()
// 當(dāng)前值減1底循,返回舊值
public final int getAndDecrement() 
// 當(dāng)前值增加delta,返回舊值
public final int getAndAdd(int delta)
// 當(dāng)前值加1,返回新值
public final int incrementAndGet() 
// 當(dāng)前值減1很魂,返回新值
public final int decrementAndGet() 
// 當(dāng)前值增加delta讶泰,返回新值
public final int addAndGet(int delta)
// 封裝了一個int對其加減
    private volatile int value;
    .......
    public final boolean compareAndSet(int expect, int update) {
    // 通過unsafe 基于CPU的CAS指令來實現(xiàn), 可以認(rèn)為無阻塞.
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    .......
    public final int getAndIncrement() {
        for (;;) {
        // 當(dāng)前值
            int current = get();
        // 預(yù)期值
            int next = current + 1;
            if (compareAndSet(current, next)) {
        // 如果加成功了, 則返回當(dāng)前值
                return current;
        }
        // 如果加失敗了, 說明其他線程已經(jīng)修改了數(shù)據(jù), 與期望不相符,
        // 則繼續(xù)無限循環(huán), 直到成功. 這種樂觀鎖, 理論上只要等兩三個時鐘周期就可以設(shè)值成功
        // 相比于直接通過synchronized獨占鎖的方式操作int, 要大大節(jié)約等待時間.
        }
    }

demo
使用10個線程打印0-10000, 最終得到結(jié)果10w.

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDemo {
    static AtomicInteger i = new AtomicInteger();

    public static class AddThread implements Runnable {
        public void run() {
            for (int k = 0; k < 10000; k++) {
                i.incrementAndGet();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] ts = new Thread[10];
        for (int k = 0; k < 10; k++) {
            ts[k] = new Thread(new AddThread());
        }
        for (int k = 0; k < 10; k++) {
            ts[k].start();
        }
        for (int k = 0; k < 10; k++) {
            ts[k].join();
        }
        System.out.println(i);
    }
}

Unsafe

Unsafe類是在sun.misc包下, 可以用于一些非安全的操作咏瑟,比如:
根據(jù)偏移量設(shè)置值, 線程park(), 底層的CAS操作等等.

 // 獲取類實例中變量的偏移量
 valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
 // 基于偏移量對值進行操作
 unsafe.compareAndSwapInt(this, valueOffset, expect, update);

主要接口

// 獲得給定對象偏移量上的int值
public native int getInt(Object o, long offset);
// 設(shè)置給定對象偏移量上的int值
public native void putInt(Object o, long offset, int x);
// 獲得字段在對象中的偏移量
public native long objectFieldOffset(Field f);
// 設(shè)置給定對象的int值,使用volatile語義
public native void putIntVolatile(Object o, long offset, int x);
// 獲得給定對象對象的int值痪署,使用volatile語義
public native int getIntVolatile(Object o, long offset);
// 和putIntVolatile()一樣码泞,但是它要求被操作字段就是volatile類型的
public native void putOrderedInt(Object o, long offset, int x);

AtomicReference
與AtomicInteger類似, 只是里面封裝了一個對象, 而不是int, 對引用進行修改

主要接口

1 get()
2 set(V)
3 compareAndSet()
4 getAndSet(V)

demo
使用10個線程, 同時嘗試修改AtomicReference中的String, 最終只有一個線程可以成功.

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceTest {
    public final static AtomicReference<String> attxnicStr = new AtomicReference<String>("abc");

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    try {
                        Thread.sleep(Math.abs((int) (Math.random() * 100)));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (attxnicStr.compareAndSet("abc", "def")) {
                        System.out.println("Thread:" + Thread.currentThread().getId() + " change value to " + attxnicStr.get());
                    } else {
                        System.out.println("Thread:" + Thread.currentThread().getId() + " change failed!");
                    }
                }
            }.start();
        }
    }
}

AtomicStampedReference
也是封裝了一個引用, 主要解決ABA問題.

ABA問題

線程一準(zhǔn)備用CAS將變量的值由A替換為B, 在此之前線程二將變量的值由A替換為C, 線程三又將C替換為A, 然后線程一執(zhí)行CAS時發(fā)現(xiàn)變量的值仍然為A, 所以線程一CAS成功.

// 比較設(shè)置 參數(shù)依次為:期望值 寫入新值 期望時間戳 新時間戳
public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp)
// 獲得當(dāng)前對象引用
public V getReference()
// 獲得當(dāng)前時間戳
public int getStamp()
// 設(shè)置當(dāng)前對象引用和時間戳
public void set(V newReference, int newStamp)

源碼分析

// 內(nèi)部封裝了一個Pair對象, 每次對對象操作的時候, stamp + 1
    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

    private volatile Pair<V> pair;

    // 進行cas操作的時候, 會對比stamp的值
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

后臺使用多個線程對用戶充值, 要求只能充值一次

public class AtomicStampedReferenceDemo {
 static AtomicStampedReference<Integer> money=new AtomicStampedReference<Integer>(19,0);
    public staticvoid main(String[] args) {
        //模擬多個線程同時更新后臺數(shù)據(jù)庫,為用戶充值
        for(int i = 0 ; i < 3 ; i++) {
            final int timestamp=money.getStamp();
            newThread() {  
                public void run() { 
                    while(true){
                       while(true){
                           Integerm=money.getReference();
                            if(m<20){
                         if(money.compareAndSet(m,m+20,timestamp,timestamp+1)){
                          System.out.println("余額小于20元狼犯,充值成功余寥,余額:"+money.getReference()+"元");
                                    break;
                                }
                            }else{
                               //System.out.println("余額大于20元,無需充值");
                                break ;
                             }
                       }
                    }
                } 
            }.start();
         }
        
       //用戶消費線程悯森,模擬消費行為
        new Thread() { 
             publicvoid run() { 
                for(int i=0;i<100;i++){
                   while(true){
                        int timestamp=money.getStamp();
                        Integer m=money.getReference();
                        if(m>10){
                             System.out.println("大于10元");
                            if(money.compareAndSet(m, m-10,timestamp,timestamp+1)){
                             System.out.println("成功消費10元宋舷,余額:"+money.getReference());
                                 break;
                             }
                        }else{
                           System.out.println("沒有足夠的金額");
                             break;
                        }
                    }
                    try {Thread.sleep(100);} catch (InterruptedException e) {}
                 }
            } 
        }.start(); 
    }
 }

AtomicIntegerArray
支持無鎖的數(shù)組

// 獲得數(shù)組第i個下標(biāo)的元素
public final int get(int i)
// 獲得數(shù)組的長度
public final int length()
// 將數(shù)組第i個下標(biāo)設(shè)置為newValue,并返回舊的值
public final int getAndSet(int i, int newValue)
// 進行CAS操作瓢姻,如果第i個下標(biāo)的元素等于expect祝蝠,則設(shè)置為update,設(shè)置成功返回true
public final boolean compareAndSet(int i, int expect, int update)
// 將第i個下標(biāo)的元素加1
public final int getAndIncrement(int i)
// 將第i個下標(biāo)的元素減1
public final int getAndDecrement(int i)
// 將第i個下標(biāo)的元素增加delta(delta可以是負(fù)數(shù))
public final int getAndAdd(int i, int delta)
// 數(shù)組本身基地址
    private static final int base = unsafe.arrayBaseOffset(int[].class);

    // 封裝了一個數(shù)組
    private final int[] array;

    static {
        // 數(shù)組中對象的寬度, int類型, 4個字節(jié), scale = 4;
        int scale = unsafe.arrayIndexScale(int[].class);
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");
        // 前導(dǎo)0 : 一個數(shù)字轉(zhuǎn)為二進制后, 他前面0的個數(shù)
        // 對于4來講, 他就是00000000 00000000 00000000 00000100, 他的前導(dǎo)0 就是29
        // 所以shift = 2
        shift = 31 - Integer.numberOfLeadingZeros(scale);
    }

    // 獲取第i個元素
    public final int get(int i) {
        return getRaw(checkedByteOffset(i));
    }

    // 第i個元素, 在數(shù)組中的偏移量是多少
    private long checkedByteOffset(int i) {
        if (i < 0 || i >= array.length)
            throw new IndexOutOfBoundsException("index " + i);

        return byteOffset(i);
    }

    // base : 數(shù)組基地址, i << shift, 其實就是i * 4, 因為這邊是int array.
    private static long byteOffset(int i) {
        // i * 4 + base
        return ((long) i << shift) + base;
    }

    // 根據(jù)偏移量從數(shù)組中獲取數(shù)據(jù)
    private int getRaw(long offset) {
        return unsafe.getIntVolatile(array, offset);
    }

Demo

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicArrayDemo {
    static AtomicIntegerArray arr = new AtomicIntegerArray(10);

    public static class AddThread implements Runnable {
        public void run() {
            for (int k = 0; k < 10000; k++) {
                arr.incrementAndGet(k % arr.length());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] ts = new Thread[10];
        for (int k = 0; k < 10; k++) {
            ts[k] = new Thread(new AddThread());
        }
        for (int k = 0; k < 10; k++) {
            ts[k].start();
        }
        for (int k = 0; k < 10; k++) {
            ts[k].join();
        }
        System.out.println(arr);
    }
}

跟之前的AtomicInteger沒太多區(qū)別幻碱,只是需要對于傳入的i需要先判斷在數(shù)組length范圍內(nèi)续膳,再轉(zhuǎn)換成內(nèi)存地址。類中其他方法類同收班。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坟岔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子摔桦,更是在濱河造成了極大的恐慌社付,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邻耕,死亡現(xiàn)場離奇詭異鸥咖,居然都是意外死亡,警方通過查閱死者的電腦和手機兄世,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門啼辣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人御滩,你說我怎么就攤上這事鸥拧〉吃叮” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵富弦,是天一觀的道長沟娱。 經(jīng)常有香客問我,道長腕柜,這世上最難降的妖魔是什么济似? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮盏缤,結(jié)果婚禮上砰蠢,老公的妹妹穿的比我還像新娘。我一直安慰自己唉铜,他們只是感情好娩脾,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著打毛,像睡著了一般柿赊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上幻枉,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天碰声,我揣著相機與錄音,去河邊找鬼熬甫。 笑死胰挑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的椿肩。 我是一名探鬼主播瞻颂,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼郑象!你這毒婦竟也來了贡这?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤厂榛,失蹤者是張志新(化名)和其女友劉穎盖矫,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體击奶,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡辈双,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了柜砾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湃望。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出证芭,到底是詐尸還是另有隱情瞳浦,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布檩帐,位于F島的核電站术幔,受9級特大地震影響另萤,放射性物質(zhì)發(fā)生泄漏湃密。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一四敞、第九天 我趴在偏房一處隱蔽的房頂上張望泛源。 院中可真熱鬧,春花似錦忿危、人聲如沸达箍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缎玫。三九已至,卻和暖如春解滓,著一層夾襖步出監(jiān)牢的瞬間赃磨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工洼裤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留邻辉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓腮鞍,卻偏偏與公主長得像值骇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子移国,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容