AtomicLong是作用是對長整形進行原子操作,顯而易見,在java1.8中新加入了一個新的原子類LongAdder,該類也可以保證Long類型操作的原子性,相對於AtomicLong,LongAdder有着更高的性能和更好的表現,可以完全替代AtomicLong的來進行原子操作。
AtomicLong的代碼很簡單,下面僅以incrementAndGet()為例,對AtomicLong的原理進行說明。
incrementAndGet()源碼如下:
public final long incrementAndGet() { for (;;) { // 獲取AtomicLong當前對應的long值 long current = get(); // 將current加1 long next = current + 1; // 通過CAS函數,更新current的值 if (compareAndSet(current, next)) return next; } } // value是AtomicLong對應的long值 private volatile long value; // 返回AtomicLong對應的long值 public final long get() { return value; } public final boolean compareAndSet(long expect, long update) { return unsafe.compareAndSwapLong(this, valueOffset, expect, update); } compareAndSet()的作用是更新AtomicLong對應的long值。它會比較AtomicLong的原始值是否與expect相等,若相等的話,則設置AtomicLong的值為update。
比AtomicLong更高效的LongAdder
AtomicLong的實現方式是內部有個value 變量,當多線程並發自增,自減時,均通過cas 指令從機器指令級別操作保證並發的原子性。
AtomicLong的實現方式是內部有個value 變量,當多線程並發自增,自減時,均通過CAS 指令從機器指令級別操作保證並發的原子性。
LongAdder是jdk8新增的用於並發環境的計數器,目的是為了在高並發情況下,代替AtomicLong/AtomicInt,成為一個用於高並發情況下的高效的通用計數器。
高並發下計數,一般最先想到的應該是AtomicLong/AtomicInt,AtmoicXXX使用硬件級別的指令 CAS 來更新計數器的值,這樣可以避免加鎖,機器直接支持的指令,效率也很高。但是AtomicXXX中的 CAS 操作在出現線程競爭時,失敗的線程會白白地循環一次,在並發很大的情況下,因為每次CAS都只有一個線程能成功,競爭失敗的線程會非常多。失敗次數越多,循環次數就越多,很多線程的CAS操作越來越接近 自旋鎖(spin lock)。計數操作本來是一個很簡單的操作,實際需要耗費的cpu時間應該是越少越好,AtomicXXX在高並發計數時,大量的cpu時間都浪費會在 自旋 上了,這很浪費,也降低了實際的計數效率。
// jdk1.8的AtomicLong的實現代碼,這段代碼在sun.misc.Unsafe中 // 當線程競爭很激烈時,while判斷條件中的CAS會連續多次返回false,這樣就會造成無用的循環,循環中讀取volatile變量的開銷本來就是比較高的 // 因為這樣,在高並發時,AtomicXXX並不是那么理想的計數方式 public final long getAndAddLong(Object o, long offset, long delta) { long v; do { v = getLongVolatile(o, offset); } while (!compareAndSwapLong(o, offset, v, v + delta)); return v; }
說LongAdder比在高並發時比AtomicLong更高效,這么說有什么依據呢?LongAdder是根據ConcurrentHashMap這類為並發設計的類的基本原理——鎖分段,來實現的,它里面維護一組按需分配的計數單元,並發計數時,不同的線程可以在不同的計數單元上進行計數,這樣減少了線程競爭,提高了並發效率。本質上是用空間換時間的思想,不過在實際高並發情況中消耗的空間可以忽略不計。
現在,在處理高並發計數時,應該優先使用LongAdder,而不是繼續使用AtomicLong。當然,線程競爭很低的情況下進行計數,使用Atomic還是更簡單更直接,並且效率稍微高一些。
既要看單線程的執行結果,還要看多線程對他的影響。
每次操作時候都要看局部變量是不是等於成員變量(判斷是否沒有別的線程干擾),最后再把局部變量賦值給成員變量完成修改。成員變量的修改都是CAS。
@SuppressWarnings("serial") abstract class Striped641 extends Number1 { static final int NCPU = Runtime.getRuntime().availableProcessors(); transient volatile Cell[] cells;// cell數組,長度一樣要是2^n,可以類比為jdk1.7的ConcurrentHashMap中的segments數組 // 累積器的基本值 ,沒有遇到並發的情況,直接使用base,速度更快; transient volatile long base;//cas更新 // 自旋標識,在對cells進行初始化,或者后續擴容時,需要通過CAS操作把此標識設置為1(busy,忙標識,相當於加鎖), 取消busy時可以直接使用cellsBusy = 0,相當於釋放鎖 transient volatile int cellsBusy;//旋轉鎖 Striped641() { } final boolean casBase(long cmp, long val) {//原子更新base return UNSAFE.compareAndSwapLong(this, BASE, cmp, val); } // 使用CAS將cells自旋標識更新為1,更新為0時可以不用CAS(賦值為0肯定是只有一個線程在賦值為0),直接使用cellsBusy就行 final boolean casCellsBusy() {//原子更新cellsBusy從0到1,以獲取鎖。 return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1); } // 下面這兩個方法是ThreadLocalRandom中的方法,不過因為包訪問關系,這里又重新寫一遍 // probe翻譯過來是探測/探測器/探針這些,不好理解,它是ThreadLocalRandom里面的一個屬性, // 不過並不影響對Striped64的理解,這里可以把它理解為線程本身的hash值 static final int getProbe() { return UNSAFE.getInt(Thread.currentThread(), PROBE); } // 相當於rehash,重新算一遍線程的hash值 static final int advanceProbe(int probe) { probe ^= probe << 13; // xorshift probe ^= probe >>> 17; probe ^= probe << 5; UNSAFE.putInt(Thread.currentThread(), PROBE, probe);//CAS設置當前線程的threadLocalRandomProbe return probe; } //x:要增加的數。fn:執行函數。uncontended=false表示更新失敗了,=true表示沒有這個線程的Cell(不可能是更新成功了,更新成功就進不來這里)。 final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {//開始分段更新:1.base更新失敗。2.前面分段更新不行或失敗。 //新建,更新,擴容,初始化,更新base。 int h;//線程hash值 // 看下ThreadLocalRandom是否初始化。如果當前線程的threadLocalRandomProbe為0,說明當前線程是第一次進入該方法, if ((h = getProbe()) == 0) {// 當前線程hash值=0, ThreadLocalRandom.current(); //初始化當前線程的PROBE值不為0, h = getProbe(); wasUncontended = true;//下面的cas語句走不走,還是間隔一個for循環在cas uncontended=false代表存在爭用,uncontended=true代表不存在爭用。 } boolean collide = false; //下面的擴容語句走不走,還是間隔一個for循環在擴容 。collide=true代表cas有沖突,collide=false代表cas無沖突 for (;;) { Cell[] as;//局部變量,線程執行時候,局部變量不會變,成員變量會改變(修改屬性地址不變,重新new地址才改變)。as一進來就賦值后面沒有更改過。 Cell a;//線程對應的cell,a一進來就賦值后面沒有更改過。 //cellsBusy沒有局部變量,直接使用成員變量。是一個鎖。 int n; long v; //------------------------------------------重要---多線程時候,在一個線程的周期里面,前一個指令的判斷(被另一個線程修改)現在不一定成立了-----------------------------------------------------// /*每次執行真正操作時候,有可能剛才判斷的條件全部不成立了,就要重來,那么剛才判斷條件有什么用:不沖突有用。 執行成功時候:在鎖住期間,剛才進來的判斷都成立,近似於單線程操作,或者別的操作了,但是不影響現在要操作的條件。*/ /*多線程同時判斷3個if,有可能一個線程判斷第一個if不成立,但是判斷第二個if時候,第一個if條件又成立了。走到后面的判斷,只能說剛才前面的判斷不成立,現在前面的判斷不一定不成立了。 所以進入一個if:要看2個判斷,之前判斷是什么,現在判斷是什么,才進入這個if條件 */ /*casCellsBusy()用於鎖住這個cells,別的線程不能擴容和初始化和新建cell(但是可以cas更新存在的cell)。但是casCellsBusy()前后的條件不一定再次成立了,所以鎖住之后要再次判斷剛才的條件 ,多線程時候上一次的判斷現在不一定成立了。*/ /*局部變量,線程執行時候,局部變量不會變,成員變量會改變(修改屬性地址不變,重新new地址才改變)。as一進來就賦值后面沒有更改過。*/ /*在一個線程里面:1.已經有cells,要么新建(新建時候看是不是空),要么更新(要看是不是原值),要么擴容(要看cells有沒有變化)。2.沒有cells就去初始化(初始話時候再看是不是空)。3.初始化搶不贏就去更新base。*/ //------------------------------------------重要-----多線程時候,在一個線程的判斷周期里面,前一個指令的判斷(被另一個線程修改)現在不一定成立了---------------------------------------------------// //as == cells,只有初始化和擴容(因為重新new)才不相等,修改值還是相等的。 //每次重新來,都會重新獲取as和a,as = cells,a = as[(n - 1) & h],並且更新線程hash。 // 1.已經有分段更新了cells!=null if ((as = cells) != null && (n = as.length) > 0) { //1.1 沒有這個線程的Cell,新建 //重新來:1.新建cell時候有人占用cell。2.新建cell時候位置不為空。 if ((a = as[(n - 1) & h]) == null) { if (cellsBusy == 0) { // cellsBusy=1表示有人在修改Cells數組(修改Cell從null到new Cell,擴容,初始化),CAS更新一個已經存在的Cell不用判斷cellsBusy。 Cell r = new Cell(x); //這期間其他線程可以做很多事 if (cellsBusy == 0 && casCellsBusy()) {// cellsBusy是0就進來,然后變成cellsBusy=1,別的進不來。 //不可能多個線程同時進這里面來。 /*鎖住cells,但是前面判斷a=null,現在不一定a=null了,因為在前面判斷到鎖住cells期間cells有可能改變了,並且cellsBusy從0變到1又變到0。所以鎖住之后在判斷是不是空。*/ /*每次執行真正操作時候,有可能剛才判斷的條件全部不成立了,就要重來,那么剛才判斷條件有什么用:不沖突有用。 執行成功時候:在鎖住期間,剛才進來的判斷都成立,近似於單線程操作,或者別的操作了,但是不影響現在要操作的條件。*/ boolean created = false; try { Cell[] rs; int m, j; // 再次判斷沒有這個cell, 前面if判斷了是空,走到這里時候有可能別人放進去了並且cellsBusy從0變到1再變到0了。如果不是null了,就不放,下次再來(直接更新)。 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; // 這個線程沒有成功創建,肯定重頭再來 } } collide = false;// cell不存在,但是有人修改cells,collide = false, } //1.2有這個線程的cell //wasUncontended=false重新來 else if (!wasUncontended) // wasUncontended=false表示更新失敗了,再來,wasUncontended=true下次不進這里直接去cas更新,否則先不cas先再來一次。 wasUncontended = true; // 1.3有這個線程的cell //wasUncontended=true,更新失敗了重新來。 else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x))))//這個線程有Cell,去更新。 break;// 更新成功,退出。 // 1.4有這個線程的cell //wasUncontended=true,更新失敗,cells初始化擴容了,重新來 else if (n >= NCPU || cells != as) // CPU能夠並行的CAS操作的最大數量是它的核心數 ,cells被改變了(擴容了肯定重新來)。 collide = false; // 1.5有這個線程的cell //wasUncontended=true,更新失敗,cells沒有初始化擴容,collide=false,重新來 else if (!collide) //=false走這里,collide=true,下次不走這里直接去擴容否則先不去擴容先再來一次。 collide = true; // 1.6有這個線程的cell //wasUncontended=true,更新失敗,cells沒有初始化擴容,collide=true,占用cells,擴容完成,重新來 //有這個線程的cell,cas失敗,說明2個線程同時更新這個cell,就擴容。既然你不讓我加,競爭這么厲害,那么擴容試試看。 else if (cellsBusy == 0 && /* 這期間其他線程可以做很多事 */casCellsBusy()) { //不可能多個線程同時進這里面來。 try { if (cells == as) { //鎖住cells了,最開始as = cells,但是現在as不一定=cells,所以判斷cells沒變擴容, Cell[] rs = new Cell[n << 1];// 執行2倍擴容 for (int i = 0; i < n; ++i) rs[i] = as[i]; cells = rs; } } finally { cellsBusy = 0;// 釋放鎖 } collide = false;// 擴容意向為false continue; // 擴容后還沒有設置值(肯定重新來) } //1.7 有這個線程的cell h = advanceProbe(h);// 修改當前線程的hash,降低hash沖突(線程hash改變是無所謂的,關注的是里面的值,與哪個線程放進去無關),避免下次還映射到這個cell。 } // 2。某個線程執行時候,前一個if判斷:沒有分段更新,cells==null或者cells.length=0。走到這里時候cells有可能不為空了,但是要進入這if必須:cellsBusy=0, //同時cells還是剛才那個as=null(沒有擴容和初始化)並且casCellsBusy()搶成功,就去初始化。 //線程執行到這里:之前判斷:【沒有cells】,並且現在【沒人擴容或者初始化,並且cells為空】就初始化。 else if (cellsBusy == 0 && cells == as && /*這期間其他線程可以做很多事*/casCellsBusy()) {// cellsBusy=1,別的線程就不能動cells //不可能多個線程同時進這里面來。 boolean init = false; try { if (cells == as) { //鎖住cells了,但是cells不一定=as=空或者null了, 鎖住之后一定要再檢測一次,如果還是null就初始化 Cell[] rs = new Cell[2];// 初始化時只創建兩個單元 rs[h & 1] = new Cell(x);// 對其中一個單元進行累積操作,另一個不管,繼續為null cells = rs; init = true; } } finally { cellsBusy = 0;// 釋放鎖 } if (init)// 初始化成功退出,初始化失敗繼續來 break; } // 3。走到這里:前面判斷不成立(不代表現在的前面判斷也不成立),之前判斷:cells=null或者cells.length=0【沒有cells】並且cellsBusy=1或者 cells被改變了【有人正在初始化或擴容】或者casCellsBusy()失敗。 //有了分段更新,還是可以用base,提高效率。准備去擴容的,但是現在有可能別人已經擴容了(cells != as)或者casCellsBusy()失敗(搶着去擴容沒有搶成功) else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x)))) break; // 更新base,成功就退出。 } /*如果Cells表為空,嘗試獲取鎖之后初始化表(初始大小為2); 如果Cells表非空,對應的Cell為空,自旋鎖未被占用,嘗試獲取鎖,添加新的Cell; 如果Cells表非空,找到線程對應的Cell,嘗試通過CAS更新該值; 如果Cells表非空,線程對應的Cell CAS更新失敗,說明存在競爭,嘗試獲取自旋鎖之后擴容,將cells數組擴大,降低每個cell的並發量后再試*/ } // double更long的邏輯基本上是一樣的 final void doubleAccumulate(double x, DoubleBinaryOperator fn, boolean wasUncontended) { int h; 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; if ((as = cells) != null && (n = as.length) > 0) { if ((a = as[(n - 1) & h]) == null) { if (cellsBusy == 0) { // Try to attach new Cell Cell r = new Cell(Double.doubleToRawLongBits(x)); 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; } else if (!wasUncontended) // CAS already known to fail wasUncontended = true; // Continue after rehash else if (a.cas(v = a.value, ((fn == null) ? Double.doubleToRawLongBits(Double.longBitsToDouble(v) + x) : Double.doubleToRawLongBits(fn.applyAsDouble(Double.longBitsToDouble(v), x))))) break; else if (n >= NCPU || cells != as) collide = false; // At max size or stale else if (!collide) collide = true; 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); } 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(Double.doubleToRawLongBits(x)); cells = rs; init = true; } } finally { cellsBusy = 0; } if (init) break; } else if (casBase(v = base, ((fn == null) ? Double.doubleToRawLongBits(Double.longBitsToDouble(v) + x) : Double.doubleToRawLongBits(fn.applyAsDouble(Double.longBitsToDouble(v), x))))) break; // Fall back on using base } } private static final sun.misc.Unsafe UNSAFE; private static final long BASE; private static final long CELLSBUSY; private static final long PROBE; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> sk = Striped641.class; BASE = UNSAFE.objectFieldOffset(sk.getDeclaredField("base")); CELLSBUSY = UNSAFE.objectFieldOffset(sk.getDeclaredField("cellsBusy")); Class<?> tk = Thread.class; PROBE = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe")); } catch (Exception e) { throw new Error(e); } } // 一個Cell里面一個value,可以看成是一個簡化的AtomicLong,通過cas操作來更新value的值 // @sun.misc.Contended是一個高端的注解,代表使用緩存行填來避免偽共享 @sun.misc.Contended static final class Cell { volatile long value;// cas更新其值 Cell(long x) { value = x; } final boolean cas(long cmp, long val) {// cas更新 return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); } 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); } } } }
public class LongAdder1 extends Striped641 implements Serializable { private static final long serialVersionUID = 7249069246863182397L; public LongAdder1() { } /*看到這里我想應該有很多人明白為什么LongAdder會比AtomicLong更高效了, 沒錯,唯一會制約AtomicLong高效的原因是高並發,高並發意味着CAS的失敗幾率更高, 重試次數更多,越多線程重試,CAS失敗幾率又越高,變成惡性循環,AtomicLong效率降低。 那怎么解決?** LongAdder給了我們一個非常容易想到的解決方案:減少並發,將單一value的更新壓力分擔到多個value中去 (每個線程更新value數組里面的自己value【多個線程訪問的同一個數組(也只有一個數組)但是cas更新的是只是其中一個value】 【因為數組有限,所以不同的線程也會出現同時cas更新一個value的情況】【會出現:多個線程同時訪問這個數組的同一個cell】, 不再是多個線程更新同一個value導致cas經常失敗), 降低單個value的 “熱度”,分段更新 */ /*這樣,線程數再多也會分擔到多個value上去更新,只需要增加value就可以降低 value的 “熱度” AtomicLong中的 惡性循環不就解決了嗎? cells 就是這個 “段” cell中的value 就是存放更新值的, 這樣,當我需要總數時,把cells 中的value都累加一下不就可以了么!!*/ /*當然,聰明之處遠遠不僅僅這里,在看看add方法中的代碼,casBase方法可不可以不要,直接分段更新,上來就計算 索引位置,然后更新value? 答案是不好,不是不行,因為,casBase操作等價於AtomicLong中的CAS操作,要知道,LongAdder這樣的處理方式是有壞處的, 分段操作必然帶來空間上的浪費,可以空間換時間,但是,能不換就不換,看空間時間都節約~! 所以,casBase操作保證了在低並發時,不會立即進入分支做分段更新操作,因為低並發時, casBase操作基本都會成功,只有並發高到一定程度了,才會進入分支, 所以,Doug Lea對該類的說明是:** 低並發時LongAdder和AtomicLong性能差不多,高並發時LongAdder更高效!***/ /*因為低並發時候,使用的是base的原子更新,沒有啟用分段更新(cells=null,並且casBase成功),高並發才啟用分段更新。*/ /*如此,longAccumulate中做了什么事,也基本略知一二了,因為cell中的value都更新失敗(說明該索引到這個cell的線程也很多 ,並發也很高時) 或者cells數組為空時才會調用longAccumulate,*/ // +x,並發計數器LongAdder加X。要么在base+x更新要么在Cell[]數組里面找到對應的Cell+x更新。 public void add(long x) {//base和cells只有一個,並且是LongAdder的屬性。 Cell[] as; long b, v; int m; Cell a; //cells!=null不用判斷后面進去(表明已經啟用了分段更新),cells=null並且base的cas更新失敗進去(表示沒有啟用分段更新但是高並發了, //需要啟用分段更新),cells=null並且base的cas更新成功就退出(沒有啟用分段更新,並且不是高並發,此時跟AotomicLong是一樣的)。 //並發時候更新失敗,AtomicLong的處理方式是死循環嘗試更新,直到成功才返回,而LongAdder則是進入這個分支。 if ((as = cells) != null || !casBase(b = base, b + x)/*cas把base的值從b變成b+x*/) { //進來:1.已經啟用分段更新了。2.沒有啟用分段更新但是cas失敗了表示高並發了。否則:沒有啟用分段更新並且不是高並發,就不進來。 boolean uncontended = true; if (as == null //cells=null進去,沒有啟用分段更新(進來了)表示高並發了。 || (m = as.length - 1) < 0 //cells.length<=0,沒有啟用分段更新(進來了)表示高並發了。 || (a = as[getProbe() & m]) == null //對as的長度取余,從as中獲取這個線程對應的a Cell。=null表示還沒有這個線程對應的cell, || !(uncontended = a.cas(v = a.value, v + x))) //a這個Cell里面的value增加x失敗, 更新成功就不會進下面了。 //1.cells=null。2.cells!=null但沒有這個線程的Cell。2.有這個線程的Cell但是更新失敗了。 longAccumulate(x, null, uncontended); //uncontended=false表示更新失敗了,=true表示沒有這個線程的Cell(不可能是更新成功了,更新成功就進不來這里)。 } } public void increment() { add(1L); } public void decrement() { add(-1L); } //將多個cell數組中的值加起來的和就類似於AtomicLong中的value // 此返回值可能不是絕對准確的,因為調用這個方法時還有其他線程可能正在進行計數累加, // 方法的返回時刻和調用時刻不是同一個點,在有並發的情況下,這個值只是近似准確的計數值 // 高並發時,除非全局加鎖,否則得不到程序運行中某個時刻絕對准確的值,但是全局加鎖在高並發情況下是下下策 // 在很多的並發場景中,計數操作並不是核心,這種情況下允許計數器的值出現一點偏差,此時可以使用LongAdder // 在必須依賴准確計數值的場景中,應該自己處理而不是使用通用的類。 public long sum() { Cell[] as = cells; Cell a; long sum = base; if (as != null) { for (int i = 0; i < as.length; ++i) { if ((a = as[i]) != null) sum += a.value; } } return sum; } public void reset() { Cell[] as = cells; Cell a; base = 0L; if (as != null) { for (int i = 0; i < as.length; ++i) { if ((a = as[i]) != null) a.value = 0L; } } } public long sumThenReset() { Cell[] as = cells; Cell a; long sum = base; base = 0L; if (as != null) { for (int i = 0; i < as.length; ++i) { if ((a = as[i]) != null) { sum += a.value; a.value = 0L; } } } return sum; } public String toString() { return Long.toString(sum()); } public long longValue() { return sum(); } public int intValue() { return (int) sum(); } public float floatValue() { return (float) sum(); } public double doubleValue() { return (double) sum(); } private static class SerializationProxy implements Serializable { private static final long serialVersionUID = 7249069246863182397L; private final long value;//LongAdder1的總和 SerializationProxy(LongAdder1 a) { value = a.sum(); } private Object readResolve() { LongAdder1 a = new LongAdder1(); a.base = value; return a; } } private Object writeReplace() { return new SerializationProxy(this); } private void readObject(java.io.ObjectInputStream s) throws java.io.InvalidObjectException { throw new java.io.InvalidObjectException("Proxy required"); } }
public abstract class Number1 implements java.io.Serializable { public abstract int intValue(); public abstract long longValue(); public abstract float floatValue(); public abstract double doubleValue(); /* System.out.println((byte)127);//127 System.out.println((byte)128);//-128 System.out.println((byte)129);//-127 System.out.println((byte)255);//-1 System.out.println((byte)256);//0 System.out.println((byte)257);//1 */ public byte byteValue() { return (byte) intValue(); } public short shortValue() { return (short) intValue(); } private static final long serialVersionUID = -8742448824652078965L; }