Deep cultivation of Java multithreading - AtomicInteger thread safety principle

Question 1: AtomicInteger thread safety principles

The basic atomic class (taking AtomicInteger as an example) is mainly implemented through the CAS spin + volatile scheme, which not only ensures the thread safety of variable operation, but also avoids the high overhead of synchronized heavyweight locks, which greatly improves the execution efficiency of Java programs. CAS is used to ensure the atomicity of variable operation, and volatile keyword is used to ensure the visibility of variables. The two are often used in combination.

Take AtomicInteger source code as an example to analyze the implementation scheme of CAS spin + volatile of atomic class:

import sun.misc.Unsafe;

public class AtomicInteger {
    private static final long serialVersionUID = 6214790243416807050L;

    // Unsafe instance
    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); }
    }

    // Internal value. volatile is used to ensure thread safety
    private volatile int value;


    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    public AtomicInteger() {
    }

    // Gets the value of the current value
    public final int get() {
        return value;
    }

    // Returns the old value and assigns a new value
    public final int getAndSet(int newValue){
        // spin
        for(;;){
            // Get old value
            int current = get();
            // Assign the value in CAS mode until it returns successfully (compare the old value with the value value of the memory address, assign a new value if it is equal, CAS succeeds, otherwise continue to execute the next cycle)
            if(compareAndSet(current,newValue)){
                return current;
            }
        }
    }

    // Encapsulate the underlying CAS operation and compare whether the expected value is equal to the value in main memory
    // Returns false if not equal
    // If equal, assign the new value to value and return true
    public final boolean compareAndSet(int expect,int update){
        return unsafe.compareAndSwapInt(this,valueOffset,expect,update);
    }
    
    // Thread safety self increment, i++
    public final int getAndIncrement(){
        // spin
        for(;;){
            int current = get();
            int next = current+1;
            if(compareAndSet(current,next)){
                return current;
            }
        }
    }
    
    // Number of custom increments
    public final int getAndAdd(int delta){
        // spin
        for(;;){
            int current = get();
            int next = current+delta;
            if(compareAndSet(current,next)){
                return current;
            }
        }
    }
    
    // Similar to + + i, it returns the value after self increment
    public final int incrementAndGet(){
        // spin
        for(;;){
            int current = get();
            int next = current+1;
            if(compareAndSet(current,next)){
                return next;
            }
        }
    }
    
    // Value after adding delta
    public final int AddAndDelta(int delta){
        // spin
        for(;;){
            int current = get();
            int next = current+delta;
            if(compareAndSet(current,next)){
                return next;
            }
        }
    }
    
    // Omit other code.....
}

Summary: the underlying principle of thread safety of AtomicXXX class is realized through CAS spin. The main operations of CAS spin are: if a CAS operation fails, obtain the latest value value, and then carry out CAS operation again until it succeeds. In addition, the internal value member wrapped by AtomicInteger is an internal member decorated with the keyword volatile, which can ensure that any thread can always get the latest value of the variable at any time. Its purpose is to ensure the thread visibility of the variable value.

be careful:

volatile only ensures the visibility of shared variables so that other threads can see the latest values, but it cannot solve the problem of instruction interleaving (atomicity cannot be guaranteed).
CAS must use volatile to read the latest value of shared variables to achieve the effect of [compare and exchange].

Question 2: why is the array in AtomicIntegerArray not decorated with volatile?

public class AtomicIntegerArray implements java.io.Serializable {
    private static final long serialVersionUID = 2862133569453604235L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final int base = unsafe.arrayBaseOffset(int[].class);
    private static final int shift;
    private final int[] array;

You know, as we mentioned before, if an array is defined as a volatile type, the array elements in it also have no volatile semantics in terms of reading and writing, that is, visibility cannot be guaranteed, so how to ensure the visibility of main memory variables?

public final int get(int i) {
    return getRaw(checkedByteOffset(i));
}

private int getRaw(long offset) {
    return unsafe.getIntVolatile(array, offset);
}

public final void set(int i, int newValue) {
    unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}

Note that the get() and set() methods in AtomicIntegerArray call methods with volatile semantics in unsafe, that is, the whole operation of array elements through memory address also has volatile semantics, that is, visibility.

Question 3: what is the principle of Atomic?

To sum up questions 1 and 2, the AtomicXXX class starting with Atomic under the Atomic package realizes thread safety through CAS spin. The main operations of CAS spin are: if a CAS operation fails, obtain the latest value value, and then carry out CAS operation again until it succeeds. CAS also ensures the visibility of shared variables in main memory with the help of volatile keyword.

Keywords: Java Back-end

Added by sapna on Sat, 05 Mar 2022 02:55:20 +0200