在進入正題以前,這裏先提出一個問題,如何在多線程中去對一個數字進行+1操做?這個問題很是簡單,哪怕是Java的初學者都能回答上來,使用AtomicXXX,好比有一個int類型的自加,那麼你可使用AtomicInteger 代替int類型進行自加。java
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:多線程
這個在代碼中不常常會有,可是有時候能夠做爲性能優化的工具出場,通常在下面兩種狀況會使用它:併發
this.variable
,可是你也想時不時的使用一下CAS操做或者原子自增操做,那麼你可使用fieldUpdater。通常有兩種狀況須要正常引用:框架
.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對象佔用內存以下:測試
關閉指針壓縮: 16(對象頭)+4(實例數據)=20不是8的倍數,所以須要對齊填充 16+4+4(padding)=24
開啓指針壓縮(-XX:+UseCompressedOop): 12+4=16已是8的倍數了,不須要再padding。
因爲咱們的AtomicInteger是一個對象,還須要被引用,那麼真實的佔用爲:
而fieldUpdater是staic final
類型並不會佔用咱們對象的內存,因此使用fieldUpdater的話能夠近似認爲只用了4字節,這個再未關閉指針壓縮的狀況下節約了7倍,關閉的狀況下節約了4倍,這個在少許對象的狀況下可能不明顯,當咱們對象有幾十萬,幾百萬,或者幾千萬的時候,節約的可能就是幾十M,幾百M,甚至幾個G。
好比在netty中的AbstractReferenceCountedByteBuf,熟悉netty的同窗都知道netty是本身管理內存的,全部的ByteBuf都會繼承AbstractReferenceCountedByteBuf,在netty中ByteBuf會被大量的建立,netty使用fieldUpdater用於節約內存。
在阿里開源的數據庫鏈接池druid中也有一樣的體現,早在2012的一個pr中,就有優化內存的comment:,在druid中,有不少統計數據對象,這些對象一般會以秒級建立,分鐘級建立新的,druid經過fieldUpdater節約了大量內存:
AtomicFieldUpdater的確在咱們平時使用比較少,可是其也值得咱們去了解,有時候在特殊的場景下的確能夠做爲奇技淫巧。
若是你們以爲這篇文章對你有幫助,你的關注和轉發是對我最大的支持,O(∩_∩)O: