Always using AtomicInteger? Try FieldUpdater

1. Background

Before entering the topic, here is a question, how to perform +1 operation on a number in multiple threads? This question is very simple, even a beginner in Java can answer it. Using AtomicXXX, for example, if there is a self-addition of int type, then you can use AtomicInteger instead of int type for self-addition.

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

As shown in the above code, using addAndGet can ensure addition in multiple threads. The specific principle is CAS at the bottom layer, so I won't go into details here. Basically AtomicXXX can meet all our needs, until a group friend (ID: Pymore) asked me a question a few days ago, he found that in many open source frameworks, such as the AbstractReferenceCountedByteBuf class in Netty defines a 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 is used by Netty to record the number of times ByteBuf is referenced. There will be concurrent operations, such as adding a reference relationship and reducing a reference relationship. Its retain method realizes the self-increment of 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;
    }

As the saying goes, if there is a cause, there must be an effect. Netty must have his own reasons for doing these things with much effort. Next, we will enter our topic.

2.Atomic field updater

There are many atomic classes in the java.util.concurrent.atomicpackage, such as AtomicInteger, AtomicLong, LongAdder, etc. are already well-known common classes. There are three other classes in this package that exist in jdk1.5, but they are often ignored by everyone. This is fieldUpdater:

  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
  • AtomicReferenceFieldUpdater

This is not often found in code, but it can sometimes be used as a performance optimization tool. It is generally used in the following two situations:

  • You want to use volatile through normal references, such as calling directly in the class this.variable, but you also want to use CAS operations or atomic increment operations from time to time, then you can use fieldUpdater.
  • When you use AtomicXXX, when there are multiple objects that refer to Atomic, you can use fieldUpdater to save memory overhead.

2.1 Normal reference to volatile variables

There are generally two situations that require normal citation:

  1. When a normal reference is introduced into the code, but a new CAS needs to be added at this time, we can replace it with the AtomicXXX object, but the previous calls have to be replaced .get()with and .set()methods, which will increase a lot of work, and also Requires extensive regression testing.
  2. The code is easier to understand. In BufferedInputStream, there is a buf array used to represent the internal buffer. It is also a volatile array. Most of the time in BufferedInputStream, you only need to use this array buffer normally. In some special cases, For example, it needs to be used when close compareAndSet, we can use AtomicReference, I think this is a bit messy, it is easier to understand using 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 Save memory

I said before that fieldUpdater can be seen in many open source frameworks. In fact, in most cases, it is to save memory. Why does it save memory?

Let's first look at the AtomicInteger class:

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;
}

There is only one member variable in AtomicInteger int value, it seems that there is no more memory, but our AtomicInteger is an object, and the correct calculation of an object should be the object header + data size. On a 64-bit machine, the AtomicInteger object occupies the following memory:

  • Turn off pointer compression: 16 (object header) + 4 (instance data) = 20 is not a multiple of 8, so you need to align padding 16 + 4 + 4 (padding) = 24

  • Turn on pointer compression (-XX:+UseCompressedOop): 12+4=16 is already a multiple of 8, no need for padding.

Since our AtomicInteger is an object and needs to be referenced, the real occupation is:

  • Turn off pointer compression: 24 + 8 = 32
  • Enable pointer compression: 16 + 4 = 20

The fieldUpdater is a staic finaltype and does not occupy the memory of our object, so if using fieldUpdater, it can be approximated that only 4 bytes are used, which saves 7 times when the pointer compression is not turned off, and saves 4 times when it is turned off. This may not be obvious in the case of a small number of objects. When we have hundreds of thousands, millions, or tens of millions of objects, the savings may be tens of M, hundreds of M, or even a few G.

For example, AbstractReferenceCountedByteBuf in netty, students familiar with netty know that netty manages memory by itself, and all ByteBufs will inherit AbstractReferenceCountedByteBuf. In netty, ByteBufs will be created in large numbers, and netty uses fieldUpdater to save memory.

The same is reflected in Alibaba's open source database connection pool Druid. As early as a pr in 2012, there was a comment on optimizing memory: , In Druid, there are many statistical data objects, these objects are usually created in seconds, minutes level to create new ones, druid saves a lot of memory through fieldUpdater:

3. Finally

AtomicFieldUpdater is indeed used less often in our daily life, but it is also worth understanding, and sometimes it can be used as a trick in special scenarios.

If you think this article is helpful to you, your attention and forwarding are the greatest support for me, O(∩_∩)O:

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324049840&siteId=291194637