Analysis of the Use of AtomicXXX Series Classes

This blog series is a summary of the learning process of concurrent programming.Because there are many articles and the writing time is scattered, I organized a catalog paste (portal) for easy reference.

Concurrent Programming Series Blog Portal

In java.util.concurrent.atomic, there are four common atomic types:

  • AtomicBoolean: Provides atomic update operations to the basic data type boolean.
  • AtomicInteger: Provides atomic update operations to the basic data type int.
  • AtomicLong: Provides atomic update operations to the basic data type long.
  • AtomicReference: This is a generic class that provides atomic update operations on reference types.

Array related operation classes are:

  • AtomicLongArray: Provides atomic update operations on elements of an int[] array.
  • AtomicIntegerArray: Provides atomic update operations on long[] array elements.
  • AtomicReferenceArray: Provides atomic update operations on array elements of reference type [].

Since the above atomic operation classes are implemented in much the same way, let's choose AtomicInteger for analysis.

code analysis

Constructor

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    //Unsafe class provides underlying CAS mechanism 
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //valueOffset is the memory address value offset of the value used to get the value in main memory
    private static final long valueOffset;
    //Get the value of valueOffset when the class loads
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    //The AtomicInteger value is stored in this variable.
    //This variable is modified with volatile and is visible
    private volatile int value;
    
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    //Default 0
    public AtomicInteger() {
    }
}

get and set method analysis

//Value is decorated with volatile to get the latest value each time
public final int get() {
    return value;
}

//value is decorated with volatile, assignment is atomic and thread safe
public final void set(int newValue) {
    value = newValue;
}

//This method may be confusing. I looked at unsafe's putOrderedInt method as follows

/**  Sets the value of the integer field at the specified offset in the
  * supplied object to the given value. This is an ordered or lazy
  * version of <code>putIntVolatile(Object,long,int)</code>, which
  * doesn't guarantee the immediate visibility of the change to other
  * threads. It is only really useful where the integer field is
  * <code>volatile</code>, and is thus expected to change unexpectedly.
  */
//The general meaning above is that the putOrderedInt method does not guarantee visibility and is only useful if the variable is volatile.
//The value variable on our side is decorated with volatile, so I think AtomicInteger's `set` method and `lazySet` method
//Functions are consistent.
public final void lazySet(int newValue) {
    unsafe.putOrderedInt(this, valueOffset, newValue);
}

//Sets the value to a given value and returns the old value
public final int getAndSet(int newValue) {
    return unsafe.getAndSetInt(this, valueOffset, newValue);
}
//Update using CAS mechanism
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//Update using CAS mechanism
public final boolean weakCompareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//CAS plus 1 and returns the original value
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
//CAS minus 1 and returns the original value
public final int getAndDecrement() {
    return unsafe.getAndAddInt(this, valueOffset, -1);
}
//CAS adds or subtracts the delta value and returns the original value
public final int getAndAdd(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta);
}
//CAS plus 1 and returns the latest value
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
//CAS minus 1 and returns the latest value
public final int decrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
//CAS adds or subtracts delta value and returns the latest value
public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

Policy Update

The following methods are not personally useful, but the difference is that the updated values are calculated through the IntUnaryOperator and IntBinaryOperator interfaces rather than in.

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

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

public final int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) {
    int prev, next;
    do {
        prev = get();
        next = accumulatorFunction.applyAsInt(prev, x);
    } while (!compareAndSet(prev, next));
    return prev;
}

 public final int accumulateAndGet(int x,IntBinaryOperator accumulatorFunction) {
    int prev, next;
    do {
        prev = get();
        next = accumulatorFunction.applyAsInt(prev, x);
    } while (!compareAndSet(prev, next));
    return next;
}

A brief summary

Overall, the AtomicBoolean, AtomicInteger, AtomicLong, and AtomicReference principles are simple: CAS is used to guarantee atomicity, volatile is used to guarantee visibility, and ultimately thread security for shared variable operations.

AtomicLongArray, AtomicIntArray, and AtomicReferenceArray are implemented slightly differently, using CAS mechanisms in conjunction with final mechanisms to achieve thread security for shared variable operations.Interested students can analyze under their own, but also relatively simple.

Tags: Java Programming

Posted on Tue, 07 Jan 2020 18:51:50 -0800 by sasa