Principle analysis of AtomicInteger

AtomicInteger is a wrapper around the Integer type that provides atomic access and update operations. Its atomic operations are implemented based on CAS. The process of CAS is as follows: when performing operations, the current data value is used as the judgment condition, and the CAS instruction is used to try to update. Get the latest value in memory before updating and compare it with the current value. If the value does not change, it indicates that no other thread has made concurrent modifications, and the update operation is successful. Otherwise, either retry or return the result.

Application scenario

The most typical application scenario of atomicinteger is counting. For example, we need to count the time taken to insert 100000 pieces of data concurrently. We need to count the inserted data. The + + operation of ordinary int variables in a multithreaded environment is thread unsafe. The previous operation may be overwritten by the latter operation, so the statistical technology is always less than the accurate value. At this point, you can use atomicinteger. Easy to use:

private AtomicInteger counter = new AtomicInteger(0);//The initial count is 0
// doSomething,After the operation is performed, the count is OK
int count = counter.incrementAndGet();

Source code analysis

Basic properties

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;
ublic final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndDecrement() {
    return unsafe.getAndAddInt(this, valueOffset, -1);
} 

It can be seen that the operation of AtomicInteger basically depends on the underlying support provided by unsafe. Unsafe will use the memory address offset of the value field to complete the operation. Enter unsafe source code:

public final int getAndSetInt(Object var1, long var2, int var3) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);//At this time of the current thread, according to AtomicInteger Object and value Memory address offset,Get value value
    } while(!this.compareAndSwapInt(var1, var2, var5,var3));
    //while condition compareAndSwapInt yes CAS Operation, if the current value is consistent with the latest value before the operation, the value Add 1, otherwise the operation fails and returns false,Continue to get the latest value until the update operation is successful.
    return var5;
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

You can see that Unsafe compareAndSwapInt uses native. The native keyword tells the compiler (actually the JVM) that the method is externally defined and is actually implemented in C language. There is no further study here.

Side effects of CAS operation

The common failure retry mechanism implies an assumption that the competition is short. In most scenarios, one or two retries are successful, but there are always exceptions. Therefore, when necessary, consider the number of spins, and do not retry after more than how many times to avoid excessive CPU consumption. Another is the famous ABA problem. CAS compares the previous value when updating. If the previous value is exactly the same as the latest value (not logically), for example, an update of a - > b - > a occurs during the update period, which may lead to unreasonable operations. In this case, ava provides AtomicStampedReference class to ensure the correctness of CAS by establishing version number for reference.

How to ensure the accuracy of the reset value

Suppose there is such a need to count the time-consuming of inserting 10w data each time. After counting to 10w, it needs to be reset to 0. First look at the code below:

private AtomicInteger counter = new AtomicInteger(0);//The initial count is 0
private long lastTime = 0;
public void insert(){
  //insert
    if(counter.incrementAndGet() == 100000) {
        counter.set(0);
        long currentTime = System.currentTimeMillis();
        log.info("\n\n=============== insert 10w data,time="+ currentTime+",used"+(currentTime-lastTime)+"'s ================\n\n");
        lastTime = currentTime;
  }
}

When the value of counter.incrementAndGet() is greater than 10w, we use the set method to reset the value to 0. In a multithreaded environment, multiple threads may execute the code counter.incrementAndGet() at the same time (the judgment of its return value = = 10w has not been executed). After the first thread is executed, it is 99999, which does not meet the conditions. The count of the following threads increases to more than 10w. At this time, the execution count result is 10w. The thread meets the conditions (= = 10w), which is reset to 0, Then several counts beyond 10w are lost. The count is not accurate. Of course, when the condition is "> =", the count is still inaccurate, and the statements that meet the condition will be executed many times and the log will be printed many times. Obviously, this is not the result we want. Is there any way to count accurately? AtomicInteger provides a updateAndGet method. The parameter is the class that implements IntUnaryOperator. Take a look at its implementation:

public final int updateAndGet(IntUnaryOperator updateFunction) {
    int prev, next;
    do {
        prev = get();
        next = updateFunction.applyAsInt(prev);
    } while (!compareAndSet(prev, next));
    return next;
}

updateFunction.applyAsInt(prev) this method returns the value we want to reset. This is simple. We just need to return the excess value from the applyAsInt method. Specific implementation code:

private AtomicInteger counter = new AtomicInteger(0);//The initial count is 0
private long lastTime = 0;
public void insert(){
  //insert
    if(counter.incrementAndGet() >= 100000) {
        counter.updateAndGet(new CounterVar());
        long currentTime = System.currentTimeMillis();
        log.info("\n\n=============== insert 10w data,time="+ currentTime+",used"+(currentTime-lastTime)+"'s ================\n\n");
        lastTime = currentTime;
  }
}

public class CounterVar implements IntUnaryOperator{
    @Override
    public int applyAsInt(int value) {
        if(value >= 100000) {
            return value-100000;
        }
        return value;
    }

}

Posted on Wed, 24 Nov 2021 02:38:56 -0500 by samvelyano