溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

一直使用AtomicInteger?試一試FieldUpdater

發(fā)布時(shí)間:2020-06-28 15:39:18 來源:網(wǎng)絡(luò) 閱讀:287 作者:艾弗森哇 欄目:編程語言

1. 背景

在進(jìn)入正題之前,這里先提出一個(gè)問題,如何在多線程中去對(duì)一個(gè)數(shù)字進(jìn)行+1操作?這個(gè)問題非常簡(jiǎn)單,哪怕是Java的初學(xué)者都能回答上來,使用AtomicXXX,比如有一個(gè)int類型的自加,那么你可以使用AtomicInteger 代替int類型進(jìn)行自加。

?AtomicInteger?atomicInteger?=?new?AtomicInteger();
????????atomicInteger.addAndGet(1);

如上面的代碼所示,使用addAndGet即可保證多線程中相加,具體原理在底層使用的是CAS,這里就不展開細(xì)講?;旧螦tomicXXX能滿足我們的所有需求,直到前幾天一個(gè)群友(ID:皮摩)問了我一個(gè)問題,他發(fā)現(xiàn)在很多開源框架中,例如Netty中的AbstractReferenceCountedByteBuf 類中定義了一個(gè)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被引用的次數(shù),會(huì)出現(xiàn)并發(fā)的操作,比如增加一個(gè)引用關(guān)系,減少一個(gè)引用關(guān)系,其retain方法,實(shí)現(xiàn)了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多費(fèi)力氣做這些事必然是有自己的原因的,接下來就進(jìn)入我們的正題。

2.Atomic field updater

java.util.concurrent.atomic包中有很多原子類,比如AtomicInteger,AtomicLong,LongAdder等已經(jīng)是大家熟知的常用類,在這個(gè)包中還有三個(gè)類在jdk1.5中都存在了,但是經(jīng)常被大家忽略,這就是fieldUpdater:

  • AtomicIntegerFieldUpdater

  • AtomicLongFieldUpdater

  • AtomicReferenceFieldUpdater

這個(gè)在代碼中不經(jīng)常會(huì)有,但是有時(shí)候可以作為性能優(yōu)化的工具出場(chǎng),一般在下面兩種情況會(huì)使用它:

  • 你想通過正常的引用使用volatile的,比如直接在類中調(diào)用this.variable,但是你也想時(shí)不時(shí)的使用一下CAS操作或者原子自增操作,那么你可以使用fieldUpdater。

  • 當(dāng)你使用AtomicXXX的時(shí)候,其引用Atomic的對(duì)象有多個(gè)的時(shí)候,你可以使用fieldUpdater節(jié)約內(nèi)存開銷。

2.1 正常引用volatile變量

一般有兩種情況需要正常引用:

  1. 當(dāng)代碼中引入已經(jīng)正常引用,但是這個(gè)時(shí)候需要新增一個(gè)CAS的需求,我們可以將其替換AtomicXXX對(duì)象,但是之前的調(diào)用都得換成.get().set()方法,這樣做會(huì)增加不少的工作量,并且還需要大量的回歸測(cè)試。

  2. 代碼更加容易理解,在BufferedInputStream中,有一個(gè)buf數(shù)組用來表示內(nèi)部緩沖區(qū),它也是一個(gè)volatile數(shù)組,在BufferedInputStream中大多數(shù)時(shí)候只需要正常的使用這個(gè)數(shù)組緩沖區(qū)即可,在一些特殊的情況下,比如close的時(shí)候需要使用compareAndSet,我們可以使用AtomicReference,我覺得這樣做有點(diǎn)亂,使用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()
????????}
????}

2.2 節(jié)約內(nèi)存

之前說過在很多開源框架中都能看見fieldUpdater的身影,其實(shí)大部分的情況都是為了節(jié)約內(nèi)存,為什么其會(huì)節(jié)約內(nèi)存呢?

我們首先來看看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成員變量只有一個(gè)int value,似乎好像并沒有多出內(nèi)存,但是我們的AtomicInteger是一個(gè)對(duì)象,一個(gè)對(duì)象的正確計(jì)算應(yīng)該是 對(duì)象頭 + 數(shù)據(jù)大小,在64位機(jī)器上AtomicInteger對(duì)象占用內(nèi)存如下:http://www.chacha8.cn/detail/1132398249.html

  • 關(guān)閉指針壓縮: 16(對(duì)象頭)+4(實(shí)例數(shù)據(jù))=20不是8的倍數(shù),因此需要對(duì)齊填充 16+4+4(padding)=24

  • 開啟指針壓縮(-XX:+UseCompressedOop): 12+4=16已經(jīng)是8的倍數(shù)了,不需要再padding。

由于我們的AtomicInteger是一個(gè)對(duì)象,還需要被引用,那么真實(shí)的占用為:

  • 關(guān)閉指針壓縮:24 + 8 = 32

  • 開啟指針壓縮: 16 + 4 = 20

而fieldUpdater是staic final類型并不會(huì)占用我們對(duì)象的內(nèi)存,所以使用fieldUpdater的話可以近似認(rèn)為只用了4字節(jié),這個(gè)再未關(guān)閉指針壓縮的情況下節(jié)約了7倍,關(guān)閉的情況下節(jié)約了4倍,這個(gè)在少量對(duì)象的情況下可能不明顯,當(dāng)我們對(duì)象有幾十萬,幾百萬,或者幾千萬的時(shí)候,節(jié)約的可能就是幾十M,幾百M(fèi),甚至幾個(gè)G。

比如在netty中的AbstractReferenceCountedByteBuf,熟悉netty的同學(xué)都知道netty是自己管理內(nèi)存的,所有的ByteBuf都會(huì)繼承AbstractReferenceCountedByteBuf,在netty中ByteBuf會(huì)被大量的創(chuàng)建,netty使用fieldUpdater用于節(jié)約內(nèi)存。鄭州不孕不育醫(yī)院有哪些:http://wapyyk.39.net/zz3/zonghe/1d427.html

在阿里開源的數(shù)據(jù)庫連接池druid中也有同樣的體現(xiàn),早在2012的一個(gè)pr中,就有優(yōu)化內(nèi)存的comment:一直使用AtomicInteger?試一試FieldUpdatercdn.xitu.io/2019/10/9/16daf616c180d4f9?w=1243&h=868&f=png&s=176909">,在druid中,有很多統(tǒng)計(jì)數(shù)據(jù)對(duì)象,這些對(duì)象通常會(huì)以秒級(jí)創(chuàng)建,分鐘級(jí)創(chuàng)建新的,druid通過fieldUpdater節(jié)約了大量內(nèi)存:?一直使用AtomicInteger?試一試FieldUpdater

3.最后

AtomicFieldUpdater的確在我們平時(shí)使用比較少,但是其也值得我們?nèi)チ私猓袝r(shí)候在特殊的場(chǎng)景下的確可以作為奇技淫巧。


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI