Atomic Integer Source Code Analysis of Dead java Atoms

(mobile phone horizontal screen source code is more convenient)

problem

(1) what is atomic manipulation?

(2) What is the relationship between atomic operation and ACID of database?

(3) How does Atomic Integer implement atomic operations?

(4) what are the shortcomings of AtomicInteger?

brief introduction

Atomic Integer is an atomic class provided under the concurrent package of java. It mainly operates on integer of int type. Atomic Integer implements atomic operation by calling the underlying Unsafe CAS method.

Remember Unsafe? Click on the link directly[ Unsafe Analysis of Dead java Magic]

Atomic operation

Atomic operations refer to operations that are not interrupted by thread scheduling mechanisms. Once they start, they run until the end without any thread context switching.

Atomic operation can be a step or multiple steps, but its order can not be disrupted, nor can it be cut to only perform a part of it, and the whole operation as a whole is the core feature of atomicity.

The biggest difference between atomic operation and atomicity in database ACID is that atomicity in database is mainly used in transactions. All update operations within a transaction either succeed or fail. Transactions have rollback mechanism, and the atomic operation we are talking about here does not roll back, which is the biggest difference.

Source code analysis

Main attributes

// Get an instance of Unsafe
private static final Unsafe unsafe = Unsafe.getUnsafe();
// Identifies the offset of the value field
private static final long valueOffset;
// Static code block to get the offset of value through unsafe
static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}
// Where values of type int are stored, decorate them with volatile
private volatile int value;

(1) use int type value storage value, and use volatile modification, volatile is mainly to ensure visibility, that is, a thread modification can be immediately visible to another thread, and the main realization principle is memory barrier.

(2) Call Unsafe's objectFieldOffset() method to get the offset of the value field in the class for later CAS operations.

compareAndSet() method

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// Method in Unsafe
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

Call the Unsafe.compareAndSwapInt() method, which has four parameters:

(1) The object of operation;

(2) The offset of the field in the object;

(3) The original value, i.e. the expected value;

(4) The value to be modified;

As you can see, this is a natural method. The bottom layer is written in C/C++, mainly by calling the C AS instructions of the CPU. It can ensure that the field values at the corresponding offset are updated only when they are expected, which is similar to the following two-step operation:

if(value == expect) {
    value = newValue;
}

The CAS instructions of the CPU can ensure that the two steps are a whole, and there will be no problem that the value value value is a when it may be compared in a multi-threaded environment, and that the value value value may become b when it is really assigned.

getAndIncrement() method

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

// Method in Unsafe
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        // This article is from the public number "Tong Ge read the source code" original.
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

At the bottom of the getAndIncrement() method is the invoked Unsafe getAndAddInt() method, which has three parameters:

(1) The object of operation;

(2) the offset of the field in the object;

(3) The value to be increased;

Looking at the source code of Unsafe's getAndAddInt() method, you can see that it first gets the current value, then calls compareAndSwapInt() to try to update the value at the corresponding offset. If it succeeds, it jumps out of the loop. If it fails, it tries again until it succeeds, which is not an optimistic locking mechanism (CAS + spin).^^

The other methods in Atomic Integer are almost identical, and ultimately call Unsafe compareAndSwapInt() to ensure the atomicity of value updates.

summary

(1) In Atomic Integer, a variable value modified with volatile is maintained to ensure visibility.

(2) The main methods in Atomic Integer almost always call Unsafe compareAndSwapInt() method to ensure the atomicity of variable modification.

Egg

(1) why do we need AtomicInteger?

Let's look at an example:

public class AtomicIntegerTest {
    private static int count = 0;

    public static void increment() {
        count++;
    }

    public static void main(String[] args) {
        IntStream.range(0, 100)
                .forEach(i->
                        new Thread(()->IntStream.range(0, 1000)
                                .forEach(j->increment())).start());

        // Use 2 or 1 here to look at your machine.
        // I'm using run to run more than 2 to exit the loop.
        // But running more than 1 with debug will exit the loop
        while (Thread.activeCount() > 1) {
            // Give up CPU
            Thread.yield();
        }

        System.out.println(count);
    }
}

There are 100 threads. Each thread adds 1000 times to count. You will find that the results of each run are different, but they all have one thing in common: they are less than 100000 times, so it is a problem to use int directly.

So, can volatile solve this problem?

private static volatile int count = 0;

public static void increment() {
    count++;
}

Unfortunately, volatile can't solve this problem, because volatile has only two functions:

(1) Guarantee visibility, that is, one thread's modifications to variables are immediately visible to another thread;

(2) prohibit instruction reordering;

Here's an important question. count++ is actually a two-step operation. The first step is to get the value of count, and the second step is to add 1 to its value.

Using volatile is not guaranteed that these two steps are not interrupted by other thread scheduling, so it is not guaranteed that atomicity.

This leads us to Atomic Integer, which calls Unsafe CAS and guarantees success with spin, which guarantees the atomicity of two-step operation.

public class AtomicIntegerTest {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void increment() {
        count.incrementAndGet();
    }

    public static void main(String[] args) {
        IntStream.range(0, 100)
                .forEach(i->
                        new Thread(()->IntStream.range(0, 1000)
                                .forEach(j->increment())).start());

        // Use 2 or 1 here to look at your machine.
        // This article is from the public number "Tong Ge read the source code" original.
        // I'm using run to run more than 2 to exit the loop.
        // But running more than 1 with debug will exit the loop
        while (Thread.activeCount() > 1) {
            // Give up CPU
            Thread.yield();
        }

        System.out.println(count);
    }
}

There's always a print of 100,000.

(2) having said so much, do you know any shortcomings of AtomicInteger?

Of course, the famous ABA problem. Let's go on to talk about it in the next chapter.^^

Welcome to pay attention to my public number "Tong Ge read the source code", see more source series articles, and swim together with brother Tong's source ocean.

Keywords: Java Database Mobile less

Added by splitinfo on Mon, 07 Oct 2019 09:20:52 +0300