Introduction to Java 16 atomic classes - based on JDK8

Keywords: Java html

Description of the number of atomic classes

Before JDK7 included 7, there were 12 java atomic classes, as shown below

  Four atomic operation classes appear in JDK8, as shown in the following picture

Atomic update base type class  

Update basic types in an Atomic way. The Atomic package provides the following three classes.

  • AtomicBoolean:   Atomic update boolean type.  
  • AtomicInteger:     Atomic update integer.  
  • AtomicLong:         Atomic update long integer.  

The above 3 categories as like as two peas are provided. The AtomicInteger is used as an example to explain the details. AtomicIngeter's common methods are as follows:  

  • int addAndGet(int delta): adds the entered value to the value in the instance in an atomic manner and returns the result.  
  • boolean compareAndSet(int expect, int update): if the current value is equal to the expected value, set the current value to the updated value atomically.  
  • int getAndIncrement(): add 1 to the current value in an atomic way. Note that the value returned here is the value before self increment, that is, the old value.  
  • void lazySet(int newValue): it will eventually be set to newValue. After setting the value with lazySet, other threads may still be able to read the old value in a short period of time.  
  • int getAndSet(int newValue): set to newValue atomically and return the old value.

Code example

	static AtomicInteger ai =new AtomicInteger(1);
	public static void main(String[] args) {
 
		System.out.println(ai.getAndIncrement());
		System.out.println(ai.get());
     }

  Output results

1

2

Let's take a look at how getAndIncrement() implements atomic operations  

 public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
 
 
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
 
        return var5;
    }

Code parsing

We get the old value, then pass the data to be added, and call getAndAddInt() to update the atom. In fact, the core method is compareAndSwapInt(), which uses CAS to update. We only provide 3 CAS operations in Unsafe,

In addition, note that AtomicBoolean converts a Boolean to an integer and operates using compareAndSwapInt.

/**
 * If the current value is var4, the atomic java variable is updated to var5 or var6
 * @return Returns true if the update is successful
 */
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
 
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
 
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

Atomic update array

Update an element in the array in an Atomic way. The Atomic package provides the following three classes:

  • AtomicIntegerArray:   Atom updates the elements in an integer array.  
  • AtomicLongArray:   Atom updates the elements in a long array.  
  • AtomicReferenceArray:   Atom updates the elements in the reference type array.

The most common methods of these three classes are the following two methods:  

  • get(int index): get the element value with index.  
  • compareAndSet(int i, int expect, int update): if the current value is equal to the expected value, set the element at array position I to the update value atomically.  

The following is an example of AtomicReferenceArray

static int[] value =new int[]{1,2};
static AtomicIntegerArray ai =new AtomicIntegerArray(value);

public static void main(String[] args) {
		ai.compareAndSet(0,1,5);
		System.out.println(ai.get(0));
		System.out.println(value[0]);
}

Output results  

5

1

It should be noted that the array value is passed in through the constructor method, and then AtomicIntegerArray will copy the current array. Therefore, when AtomicIntegerArray modifies the internal array elements, the passed in array will not be affected

 public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();
    }

Atomic update reference type

AtomicInteger of Atomic update basic type can only update one value. If you update multiple values, such as the value in an object, you need to update the class provided by the reference type with atom. The Atomic package provides the following three classes:

  • AtomicReference:   Atomic update reference type.  
  • AtomicReferenceFieldUpdater:   Fields of atomic update reference type.  
  • AtomicMarkableReferce:   Atom updates reference types with tag bits. You can update tag bits and reference types of a boolean type using a constructor.  
  public static AtomicReference<User> ai = new AtomicReference<User>();
 
    public static void main(String[] args) {
 
        User u1 = new User("pangHu", 18);
        ai.set(u1);
        User u2 = new User("piKaQiu", 15);
        ai.compareAndSet(u1, u2);
        System.out.println(ai.get().getAge() + ai.get().getName());
 
    }
 
 static class User {
        private String name;
        private int age;
 
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
 
        public String getName() {
            return name;
        }
 
        public void setName(String name) {
            this.name = name;
        }
 
        public int getAge() {
            return age;
        }
 
        public void setAge(int age) {
            this.age = age;
        }
    }
 

Output result: piKaQiu 15

code analysis

We put the object in   AtomicReference, and then call   compareAndSet() atomic operation substitution, principle and   AtomicInteger is the same, but compareAndSwapObject() is called   method.

Atomic update field class

If you need to update a field in the Atomic class, you need to use the Atomic update field class. The Atomic package provides three classes for Atomic field update:  

  • AtomicIntegerFieldUpdater:   An updater that updates fields of an atomic integer.  
  • AtomicLongFieldUpdater:   An updater that updates long fields atomically.  
  • AtomicStampedFieldUpdater:   Atomic updates reference types with version numbers.  
 //Create an atomic updater and set the object class and object properties that need to be updated
    private static AtomicIntegerFieldUpdater<User> ai = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
 
    public static void main(String[] args) {
 
        User u1 = new User("pangHu", 18);
        //Atomic renewal age, + 1
        System.out.println(ai.getAndIncrement(u1));
        System.out.println(u1.getAge());
    }
 
 
 
static class User {
        private String name;
        public volatile int age;
 
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
 
        public String getName() {
            return name;
        }
 
        public void setName(String name) {
            this.name = name;
        }
 
        public int getAge() {
            return age;
        }
 
        public void setAge(int age) {
            this.age = age;
        }
    }

Code explanation

To update field classes atomically, you need two steps.

  1. Because atomic update field classes are abstract classes, you must use the static method newUpdater() to create an updater every time you use it, and you need to set the classes and properties you want to update.
  2. The fields of the update class must be decorated with public volatile.

Output results:

18

19

Introduction to JDK8 new atomic class  

  • DoubleAccumulator
  • LongAccumulator
  • DoubleAdder
  • LongAdder

Let's take LongAdder as an example and list the precautions

These classes correspond to improvements to classes such as AtomicLong. For example, LongAccumulator and LongAdder are more efficient than AtomicLong in high concurrency environment.

Atomic and Adder have similar performance in low concurrency environment. However, in the high concurrency environment, Adder has significantly higher throughput, but higher spatial complexity.

LongAdder is actually a special case of LongAccumulator. Calling LongAdder is equivalent to calling LongAccumulator in the following way.

sum()   Method is called without concurrency. If it is used in concurrency, there will be inaccurate counting. The following code is an example.

LongAdder cannot replace AtomicLong  , Although the add() method of LongAdder can operate atomically, it does not use the CAS algorithm of Unsafe, but uses the idea of CAS.

LongAdder is actually a special case of LongAccumulator. Calling LongAdder is equivalent to calling LongAccumulator in the following way. LongAccumulator provides more powerful functions than LongAdder. Among them, accumulator function is a binocular operator interface, which returns a calculated value according to the two input parameters, and identity is the initial value of LongAccumulator accumulator.

private static ExecutorService executorService = Executors.newFixedThreadPool(5);
    public static void main(String[] args) {
        for (int i = 1; i <= 100; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    counter.add(2);
                }
            });
        }
        System.out.println(counter.sum());
        System.out.println(counter);
    }

Output results

As shown in the figure, LongAdder internally maintains multiple variables, and each variable is initialized to 0. Under the condition of the same concurrency, the number of threads competing for a single variable will be reduced, which reduces the concurrency of competing for shared resources in a disguised way. In addition, if multiple threads fail to compete for the same atomic variable, they do not try to retry, but try to obtain the locks of other atomic variables, Finally, when obtaining the current value, it is returned after accumulating the values of all variables.

//Constructor
LongAdder()
    //Create a new adder with an initial sum of zero.
 
//Method summary
void    add(long x)    //Adds the given value.
void    decrement()    //Equivalent to add(-1).
double  doubleValue() //Return sum()as double after extending the original transformation.
float   floatValue()  //After extending the original transformation, return sum()as float.
void    increment()  //Equivalent to add(1).
int intValue()      //Return sum() as int after a basic contraction conversion.
long    longValue() //Equivalent to sum().
void    reset()    //Resets variables that keep the sum to zero.
long    sum()     //Returns the current total.
long    sumThenReset()  //Equivalent to the effect reset() after sum().
String  toString()   //return. The string representation of sum().

Posted by smarlowe on Fri, 01 Oct 2021 18:38:20 -0700