您好,登錄后才能下訂單哦!
在進入正題之前,這里先提出一個問題,如何在多線程中去對一個數字進行+1操作?這個問題非常簡單,哪怕是Java的初學者都能回答上來,使用AtomicXXX,比如有一個int類型的自加,那么你可以使用AtomicInteger 代替int類型進行自加。
?AtomicInteger?atomicInteger?=?new?AtomicInteger(); ????????atomicInteger.addAndGet(1);
如上面的代碼所示,使用addAndGet即可保證多線程中相加,具體原理在底層使用的是CAS,這里就不展開細講。基本上AtomicXXX能滿足我們的所有需求,直到前幾天一個群友(ID:皮摩)問了我一個問題,他發現在很多開源框架中,例如Netty中的AbstractReferenceCountedByteBuf 類中定義了一個refCntUpdater:
????private?static?final?AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf>?refCntUpdater;????static?{ ????????AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf>?updater?= ????????????????PlatformDependent.newAtomicIntegerFieldUpdater(AbstractReferenceCountedByteBuf.class,?"refCnt");????????if?(updater?==?null)?{ ????????????updater?=?AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class,?"refCnt"); ????????} ????????refCntUpdater?=?updater; ????}
refCntUpdater 是Netty用來記錄ByteBuf被引用的次數,會出現并發的操作,比如增加一個引用關系,減少一個引用關系,其retain方法,實現了refCntUpdater的自增:
????private?ByteBuf?retain0(int?increment)?{????????for?(;;)?{????????????int?refCnt?=?this.refCnt;????????????final?int?nextCnt?=?refCnt?+?increment;????????????//?Ensure?we?not?resurrect?(which?means?the?refCnt?was?0)?and?also?that?we?encountered?an?overflow. ????????????if?(nextCnt?<=?increment)?{????????????????throw?new?IllegalReferenceCountException(refCnt,?increment); ????????????}????????????if?(refCntUpdater.compareAndSet(this,?refCnt,?nextCnt))?{????????????????break; ????????????} ????????}????????return?this; ????}
俗話說有因必有果,netty多費力氣做這些事必然是有自己的原因的,接下來就進入我們的正題。
在java.util.concurrent.atomic
包中有很多原子類,比如AtomicInteger,AtomicLong,LongAdder等已經是大家熟知的常用類,在這個包中還有三個類在jdk1.5中都存在了,但是經常被大家忽略,這就是fieldUpdater:
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
這個在代碼中不經常會有,但是有時候可以作為性能優化的工具出場,一般在下面兩種情況會使用它:
你想通過正常的引用使用volatile的,比如直接在類中調用this.variable
,但是你也想時不時的使用一下CAS操作或者原子自增操作,那么你可以使用fieldUpdater。
當你使用AtomicXXX的時候,其引用Atomic的對象有多個的時候,你可以使用fieldUpdater節約內存開銷。
一般有兩種情況需要正常引用:
當代碼中引入已經正常引用,但是這個時候需要新增一個CAS的需求,我們可以將其替換AtomicXXX對象,但是之前的調用都得換成.get()
和.set()
方法,這樣做會增加不少的工作量,并且還需要大量的回歸測試。
代碼更加容易理解,在BufferedInputStream
中,有一個buf數組用來表示內部緩沖區,它也是一個volatile數組,在BufferedInputStream中大多數時候只需要正常的使用這個數組緩沖區即可,在一些特殊的情況下,比如close的時候需要使用compareAndSet
,我們可以使用AtomicReference,我覺得這樣做有點亂,使用fieldUpdater來說更加容易理解,
????protected?volatile?byte?buf[];????private?static?final ????????AtomicReferenceFieldUpdater<BufferedInputStream,?byte[]>?bufUpdater?= ????????AtomicReferenceFieldUpdater.newUpdater ????????(BufferedInputStream.class,??byte[].class,?"buf");???????? ????public?void?close()?throws?IOException?{????????byte[]?buffer;????????while?(?(buffer?=?buf)?!=?null)?{????????????if?(bufUpdater.compareAndSet(this,?buffer,?null))?{ ????????????????InputStream?input?=?in; ????????????????in?=?null;????????????????if?(input?!=?null) ????????????????????input.close();????????????????return; ????????????}????????????//?Else?retry?in?case?a?new?buf?was?CASed?in?fill() ????????} ????}
之前說過在很多開源框架中都能看見fieldUpdater的身影,其實大部分的情況都是為了節約內存,為什么其會節約內存呢?
我們首先來看看AtomicInteger類:
public?class?AtomicInteger?extends?Number?implements?java.io.Serializable?{????private?static?final?long?serialVersionUID?=?6214790243416807050L;????//?setup?to?use?Unsafe.compareAndSwapInt?for?updates ????private?static?final?Unsafe?unsafe?=?Unsafe.getUnsafe();????private?static?final?long?valueOffset;????static?{????????try?{ ????????????valueOffset?=?unsafe.objectFieldOffset ????????????????(AtomicInteger.class.getDeclaredField("value")); ????????}?catch?(Exception?ex)?{?throw?new?Error(ex);?} ????}????private?volatile?int?value; }
在AtomicInteger成員變量只有一個int value
,似乎好像并沒有多出內存,但是我們的AtomicInteger是一個對象,一個對象的正確計算應該是 對象頭 + 數據大小,在64位機器上AtomicInteger對象占用內存如下:http://www.chacha8.cn/detail/1132398249.html
關閉指針壓縮: 16(對象頭)+4(實例數據)=20不是8的倍數,因此需要對齊填充 16+4+4(padding)=24
開啟指針壓縮(-XX:+UseCompressedOop): 12+4=16已經是8的倍數了,不需要再padding。
由于我們的AtomicInteger是一個對象,還需要被引用,那么真實的占用為:
關閉指針壓縮:24 + 8 = 32
開啟指針壓縮: 16 + 4 = 20
而fieldUpdater是staic final
類型并不會占用我們對象的內存,所以使用fieldUpdater的話可以近似認為只用了4字節,這個再未關閉指針壓縮的情況下節約了7倍,關閉的情況下節約了4倍,這個在少量對象的情況下可能不明顯,當我們對象有幾十萬,幾百萬,或者幾千萬的時候,節約的可能就是幾十M,幾百M,甚至幾個G。
比如在netty中的AbstractReferenceCountedByteBuf,熟悉netty的同學都知道netty是自己管理內存的,所有的ByteBuf都會繼承AbstractReferenceCountedByteBuf,在netty中ByteBuf會被大量的創建,netty使用fieldUpdater用于節約內存。鄭州不孕不育醫院有哪些:http://wapyyk.39.net/zz3/zonghe/1d427.html
在阿里開源的數據庫連接池druid中也有同樣的體現,早在2012的一個pr中,就有優化內存的comment:cdn.xitu.io/2019/10/9/16daf616c180d4f9?w=1243&h=868&f=png&s=176909">,在druid中,有很多統計數據對象,這些對象通常會以秒級創建,分鐘級創建新的,druid通過fieldUpdater節約了大量內存:?
AtomicFieldUpdater的確在我們平時使用比較少,但是其也值得我們去了解,有時候在特殊的場景下的確可以作為奇技淫巧。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。