一直使用AtomicInteger?試一試FieldUpdater

1. 背景

在進入正題以前,這裏先提出一個問題,如何在多線程中去對一個數字進行+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多費力氣作這些事必然是有本身的緣由的,接下來就進入咱們的正題。性能優化

2.Atomic field updater

java.util.concurrent.atomic包中有不少原子類,好比AtomicInteger,AtomicLong,LongAdder等已是你們熟知的經常使用類,在這個包中還有三個類在jdk1.5中都存在了,可是常常被你們忽略,這就是fieldUpdater:多線程

  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
  • AtomicReferenceFieldUpdater

這個在代碼中不常常會有,可是有時候能夠做爲性能優化的工具出場,通常在下面兩種狀況會使用它:併發

  • 你想經過正常的引用使用volatile的,好比直接在類中調用this.variable,可是你也想時不時的使用一下CAS操做或者原子自增操做,那麼你可使用fieldUpdater。
  • 當你使用AtomicXXX的時候,其引用Atomic的對象有多個的時候,你可使用fieldUpdater節約內存開銷。

2.1 正常引用volatile變量

通常有兩種狀況須要正常引用:框架

  1. 當代碼中引入已經正常引用,可是這個時候須要新增一個CAS的需求,咱們能夠將其替換AtomicXXX對象,可是以前的調用都得換成.get().set()方法,這樣作會增長很多的工做量,而且還須要大量的迴歸測試。
  2. 代碼更加容易理解,在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()
        }
    }

2.2 節約內存

以前說過在不少開源框架中都能看見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是一個對象,還須要被引用,那麼真實的佔用爲:

  • 關閉指針壓縮: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用於節約內存。

在阿里開源的數據庫鏈接池druid中也有一樣的體現,早在2012的一個pr中,就有優化內存的comment:,在druid中,有不少統計數據對象,這些對象一般會以秒級建立,分鐘級建立新的,druid經過fieldUpdater節約了大量內存:

3.最後

AtomicFieldUpdater的確在咱們平時使用比較少,可是其也值得咱們去了解,有時候在特殊的場景下的確能夠做爲奇技淫巧。

若是你們以爲這篇文章對你有幫助,你的關注和轉發是對我最大的支持,O(∩_∩)O:

相關文章
相關標籤/搜索