LongAdder簡單介紹

LongAdder簡單介紹

在原子操作類AtomicLong中,在高并發(fā)的情況,會出現(xiàn)大量的線程去爭搶更新同一個原子變量,但是同時只能有一個線程CAS操作成功,這就會出現(xiàn)大量線程爭搶失敗,然后通過自旋進行重試,這就會消耗CPU的資源;這個時候就出現(xiàn)了LongAdder原子操作類;它主要就是將這里的原子變量復(fù)制成多個,然后多個變量由線程去爭,結(jié)束的時候,再進行多個變量值的計算;

先了解一下LongAdder的結(jié)構(gòu)
1647521009246.jpg

從上圖可以看出LongAdder是繼承了Striped64這個類;其中定義了一個Cell類型的數(shù)組,Cell的具體結(jié)構(gòu)如下:

    @sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

1啥容、該類使用了@sun.misc.Contended注解,是為了解決偽共享問題; 2、該類內(nèi)部定義了一個volatile修飾的long類型的value變量; 3、該類內(nèi)部定義了一個cas(long cmp, long val)方法; 4、該類內(nèi)部通過sun.misc.Unsafe類獲取該類中value變量的偏移量;

因為每個Cell內(nèi)部都維護了一個變量,所以在高并發(fā)情況下,不同的線程,會訪問不同的Cell內(nèi)部的long類型變量,而且當(dāng)某一個線程在變更某一個Cell中的變量時,并不會在此Cell上進行自旋重試,而是嘗試在其他的Cell變量上重試;最后將所有的value變量值sum后再加上base返回;

接下來簡單了解一下LongAdder中比較重要的兩方法add(long x)和longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended)

  • void add(long x)
    public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        //(1)
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            //(2)
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

(1) 中判斷cells數(shù)組是否為null,CAS操作base的值是否成功;一般的在并發(fā)不高的情況下,都是通過CAS操作直接修改base的值來完成;在并發(fā)高的情況下,casBase(b = base, b + x)方法可能會出現(xiàn)失敗,這個時候就會使用cells數(shù)組;

(2) 中一共有三個判斷

情況1迎膜、as == null || (m = as.length - 1) < 0; 這里的as是對cells的引用,如果成立表示此時cells數(shù)據(jù)還未進行初始化;這里的m是cells數(shù)組長度-1,如果m<0成立,證明此時cells數(shù)組的長度小于1,則可以理解此時cells數(shù)據(jù)還未初始化,然后進入到了longAccumulate方法中;

情況2诉位、(a = as[getProbe() & m]) == null; 這里的getProbe()方法是獲取當(dāng)前線程的hash值,a的值是當(dāng)前線程的hash值在cells數(shù)組中對應(yīng)位置的值也就是Cell,如果為null,則證明當(dāng)前線程還未對cells數(shù)組中的元素進行操作,然后進入到了longAccumulate方法中;

      static final int getProbe() {
  return UNSAFE.getInt(Thread.currentThread(), PROBE);
}

情況3鲫尊、!(uncontended = a.cas(v = a.value, v + x)); 這里的a為當(dāng)前線程對應(yīng)的Cell, 然后對應(yīng)的CAS操作當(dāng)前的Cell中的value值,如果不成功,則進入到longAccumulate方法中

  • void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended)
    final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
                //(3)
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); // force initialization
            h = getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            //(4)
            if ((as = cells) != null && (n = as.length) > 0) {
                //(4.1)
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                //(4.2)
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                //(4.3)
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                //(4.4)
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                //(4.5)
                else if (!collide)
                    collide = true;
                //(4.6)
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h);
            }
            //(5)
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            //(6)
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

該方法結(jié)合(2)中的三種情況來分析

情況1進入:此時cells數(shù)組還未初始化,執(zhí)行到(3)的時候h = getProbe()為當(dāng)前線程的hash值,因為當(dāng)前線程還未對threadLocalRandomProbe值做任何操作,所以它肯定是0;所以(h = getProbe()) == 0;成立! 然后調(diào)用ThreadLocalRandom.current();

public static ThreadLocalRandom current() {
  if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
      localInit();
  return instance;
}
static final void localInit() {
  int p = probeGenerator.addAndGet(PROBE_INCREMENT);
  int probe = (p == 0) ? 1 : p; // skip 0
  long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
  Thread t = Thread.currentThread();
  UNSAFE.putLong(t, SEED, seed);
  UNSAFE.putInt(t, PROBE, probe);
}

到這里才對threadLocalRandomProbe進行了操作,然后在h = getProbe();獲取最新的值; 這個時候因為as == cells == null,所以直接到(5);在(5)中cellsBusy是一個自旋鎖,當(dāng)?shù)扔?時,證明是無鎖狀態(tài),可以獲取鎖;cells == as判斷當(dāng)前的cells是不是沒有變動;然后再通過casCellsBusy()方法進行加鎖,將cellsBusy賦值為1;在(5)的內(nèi)部,先初始化一個大小為2的Cell[]數(shù)組,然后將通過位于運算將第0個或者第一個1元素的值設(shè)置為x的值,然后將這個新的數(shù)據(jù)賦值給cells數(shù)組,這個時候cells就不是空的了;最后釋放鎖,跳出循環(huán)結(jié)束;

情況二進入:此時cells數(shù)組肯定不為空,但是當(dāng)前線程在cells中還沒有設(shè)置過一個Cell元素;這個時候就到了(4)這一步,這個時候執(zhí)行a = as[(n - 1) & h]==null判斷,這里的h是當(dāng)前線程調(diào)用了ThreadLocalRandom.current()之后的hash值,在做一次判斷是否為null;

假設(shè)(4.1)成立;執(zhí)行cellsBusy==0獲取鎖,嘗試去new一個Cell對象,并且賦值為x;然后在嘗試獲取鎖,并且通過CAS修改鎖的狀態(tài),最后再次判斷當(dāng)前cells是否為空,當(dāng)前線程是否沒有對應(yīng)的Cell元素,然后將新new的元素賦值給當(dāng)前這個線程在cells數(shù)組中的對應(yīng)位置的元素;最后釋放鎖結(jié)束;

假設(shè)(4.1)不成立;證明當(dāng)前線程有對應(yīng)的Cell元素,這個時候的wasUncontended=true;所以不會進入(4.2),所以看(4.3),此時的fn是等于null的煤辨,所以(4.3)就是a.cas(v = a.value, v + x),也就是將當(dāng)前線程對應(yīng)的Cell對象中的原子變量進行CAS操作,如果CAS成功結(jié)束循環(huán);

假設(shè)(4.3)不成立;在(4.4)中設(shè)置collide = false;或者(4.5)中設(shè)置collide = true后,執(zhí)行h = advanceProbe(h);重新設(shè)置當(dāng)前線程的hash值,然后繼續(xù)下一輪循環(huán);

假設(shè)(4.6)成立;這里是是將cells數(shù)組進行擴容,然后繼續(xù)下一輪循環(huán);

情況三進入:此時對應(yīng)線程是有cell元素的,只是cas失敗了;然后進入(4.3)或者(4.6)繼續(xù)循環(huán);

代碼(6)中是直接進行CAS操作base值;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者诀拭。
  • 序言:七十年代末迁筛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子耕挨,更是在濱河造成了極大的恐慌细卧,老刑警劉巖尉桩,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贪庙,居然都是意外死亡魄健,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門插勤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人革骨,你說我怎么就攤上這事农尖。” “怎么了良哲?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵盛卡,是天一觀的道長。 經(jīng)常有香客問我筑凫,道長滑沧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任巍实,我火速辦了婚禮滓技,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棚潦。我一直安慰自己令漂,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布丸边。 她就那樣靜靜地躺著叠必,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妹窖。 梳的紋絲不亂的頭發(fā)上纬朝,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機與錄音骄呼,去河邊找鬼共苛。 笑死,一個胖子當(dāng)著我的面吹牛谒麦,可吹牛的內(nèi)容都是我干的俄讹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼绕德,長吁一口氣:“原來是場噩夢啊……” “哼患膛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起耻蛇,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤踪蹬,失蹤者是張志新(化名)和其女友劉穎胞此,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跃捣,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡漱牵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了疚漆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酣胀。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖娶聘,靈堂內(nèi)的尸體忽然破棺而出闻镶,到底是詐尸還是另有隱情,我是刑警寧澤丸升,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布铆农,位于F島的核電站,受9級特大地震影響狡耻,放射性物質(zhì)發(fā)生泄漏墩剖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一夷狰、第九天 我趴在偏房一處隱蔽的房頂上張望岭皂。 院中可真熱鬧,春花似錦沼头、人聲如沸蒲障。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揉阎。三九已至,卻和暖如春背捌,著一層夾襖步出監(jiān)牢的瞬間毙籽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工毡庆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留坑赡,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓么抗,卻偏偏與公主長得像毅否,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蝇刀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349

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