Atomic

Atomic

1 Introduction to Atomic Atomic classes

Chemically, we know that atoms are the smallest unit of a general substance and are inseparable in chemical reactions. In our case Atomic means that an operation is uninterruptible. Even when multiple threads are executed together, once an operation starts, it will not be disturbed by other threads.

Therefore, the so-called atomic class is simply a class with atomic/atomic operational characteristics.

The atomic classes of the concurrent package java.util.concurrent are stored in java.util.concurrent.atomic as shown in the following figure.

According to the data type of the operation, the atomic classes in the juc package can be divided into 4 categories.

basic type

Update the basic type using atomic methods

  • AtomicInteger: Integer atom class
  • AtomicLong: Long integer atom class
  • Atomicboolean : boolean atom class

Array type

Update an element in an array using atoms

  • AtomicIntegerArray: integer array atom class
  • AtomicLongArray: long integer array atom class
  • AtomicReferenceArray : reference type array atom class

Reference Type

  • AtomicReference: reference type atom class
  • AtomicStampedReference: The atomic class of the field in the atomic update reference type
  • AtomicMarkableReference : atomic update reference type with tag bit

Attribute modification type of object

  • AtomicIntegerFieldUpdater: Updater for atomic update integer fields
  • AtomicLongFieldUpdater: Updater for atomic update long field
  • AtomicStampedReference: Atomic update reference type with version number. This class associates an integer value with a reference and can be used to resolve the atomic update data and the version number of the data. It can solve the ABA problem that may occur when using CAS for atomic update.
  • AtomicMarkableReference: The atomic update has a reference type with a tag. This class associates boolean tags with references and can also solve ABA problems that may occur when using CAS for atomic updates.

CAS ABA issue - Description: The first thread takes the value A of the variable x, and then Barabara does something else. In short, I only get the value A of the variable x. During this time, the second thread also takes the value A of the variable x, then changes the value of the variable x to B, then Barabara does something else, and finally changes the value of the variable x to A (equivalent to restore). . After this, the first thread finally performed the operation of the variable x, but at this time the value of the variable x is still A, so the compareAndSet operation is successful. - Example description (may not be appropriate, but well understood): At the beginning of the year, the cash was zero, then earned 3 million through normal labor, and then normal consumption (such as buying a house) 3 million. At the end of the year, although the cash is zero income (may become other forms), it is a fact to make money, but still have to pay taxes! - Code example (take AtomicInteger as an example)

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDefectDemo {
    public static void main(String[] args) {
        defectOfABA();
    }

    static void defectOfABA() {
        final AtomicInteger atomicInteger = new AtomicInteger(1);

        Thread coreThread = new Thread(
                () -> {
                    final int currentValue = atomicInteger.get();
                    System.out.println(Thread.currentThread().getName() + " ------ currentValue=" + currentValue);

                    // This purpose: Simulate the time spent processing other business
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    boolean casResult = atomicInteger.compareAndSet(1, 2);
                    System.out.println(Thread.currentThread().getName()
                            + " ------ currentValue=" + currentValue
                            + ", finalValue=" + atomicInteger.get()
                            + ", compareAndSet Result=" + casResult);
                }
        );
        coreThread.start();

        // This purpose: in order to let the coreThread thread run first
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread amateurThread = new Thread(
                () -> {
                    int currentValue = atomicInteger.get();
                    boolean casResult = atomicInteger.compareAndSet(1, 2);
                    System.out.println(Thread.currentThread().getName()
                            + " ------ currentValue=" + currentValue
                            + ", finalValue=" + atomicInteger.get()
                            + ", compareAndSet Result=" + casResult);

                    currentValue = atomicInteger.get();
                    casResult = atomicInteger.compareAndSet(2, 1);
                    System.out.println(Thread.currentThread().getName()
                            + " ------ currentValue=" + currentValue
                            + ", finalValue=" + atomicInteger.get()
                            + ", compareAndSet Result=" + casResult);
                }
        );
        amateurThread.start();
    }
}

The output is as follows:

Thread-0 ------ currentValue=1
Thread-1 ------ currentValue=1, finalValue=2, compareAndSet Result=true
Thread-1 ------ currentValue=2, finalValue=1, compareAndSet Result=true
Thread-0 ------ currentValue=1, finalValue=2, compareAndSet Result=true

Let’s take a closer look at these atomic classes.

2 Basic type atom class

2.1 Basic Type Atomic class Introduction

Update the basic type using atomic methods

  • AtomicInteger: Integer atom class
  • AtomicLong: Long integer atom class
  • Atomicboolean : boolean atom class

The methods provided by the above three classes are almost the same.

AtomicInteger class common method

public final int get() //Get the current value
public final int getAndSet(int newValue)//Get the current value and set the new value
public final int getAndIncrement()//Get the current value and increment
public final int getAndDecrement() //Get the current value and decrement it
public final int getAndAdd(int delta) //Get the current value and add the expected value
boolean compareAndSet(int expect, int update) //Atomically set the value to the input value if the value entered is equal to the expected value (update)
public final void lazySet(int newValue)// is finally set to newValue. Setting with lazySet may cause other threads to read the old value for a short period of time.

2.2 AtomicInteger Common method use

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int temvalue = 0;
		AtomicInteger i = new AtomicInteger(0);
		temvalue = i.getAndSet(3);
		System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:0; i:3
		temvalue = i.getAndIncrement();
		System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:3; i:4
		temvalue = i.getAndAdd(5);
		System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:4; i:9
	}

}

2.3 Advantages of Basic Data Type Atomic Classes

Take a look at the advantages of the basic data type atom class with a simple example.

1 multi-threaded environment does not use atomic classes to ensure thread safety (basic data types)

class Test {
        private volatile int count = 0;
        // If you want to thread safely execute count++, you need to lock
        public synchronized void increment() {
            count++; 
        }

        public int getCount() {
            return count;
        }
}

2 multi-threaded environment uses atomic classes to ensure thread safety (basic data types)

class Test2 {
        private AtomicInteger count = new AtomicInteger();

        public void increment() {
            count.incrementAndGet();
        }
        // After using AtomicInteger, you do not need to lock, you can also achieve thread safety.
       public int getCount() {
            return count.get();
        }
}

2.4 AtomicInteger Simple analysis of thread safety principles

Part of the source code for the AtomicInteger class:

    // setup to use Unsafe.compareAndSwapint for updates (provide "compare and replace" when updating operations)
    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;

The AtomicInteger class mainly uses CAS (compare and swap) + volatile and native methods to guarantee atomic operations, thus avoiding the high overhead of synchronized and greatly improving the execution efficiency.

The principle of CAS is to compare the expected value with the original value, and if it is the same, update it to a new value. The objectFieldOffset() method of the UnSafe class is a local method that is used to get the memory address of the “original value”. In addition, value is a volatile variable that is visible in memory, so the JVM can guarantee that any thread will always get the latest value of the variable at any time.

3 Array type atom class

3.1 Introduction to Array Type Atomic Classes

Update an element in an array using atoms

  • AtomicIntegerArray: shaping array atom class
  • AtomicLongArray: long integer array atom class
  • AtomicReferenceArray : reference type array atom class

The methods provided by the above three classes are almost the same, so we will introduce AtomicIntegerArray as an example here.

AtomicIntegerArray class common method

public final int get(int i) //Get the value of the index=i position element
public final int getAndSet(int i, int newValue)//returns the current value of the index=i position and sets it to the new value: newValue
public final int getAndIncrement(int i)//Get the value of the index=i positional element and let the element of the position increase
public final int getAndDecrement(int i) //Get the value of the index=i position element and let the element at that position decrement
public final int getAndAdd(int delta) //Get the value of the index=i positional element and add the expected value
boolean compareAndSet(int expect, int update) //If the value entered is equal to the expected value, atomically set the element value at index=i to the input value (update)
public final void lazySet(int i, int newValue)// Finally sets the element at index=i to newValue. Setting with lazySet may cause other threads to read the old value for a short period of time.

3.2 AtomicIntegerArray Common method use

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int temvalue = 0;
		Int[] nums = { 1, 2, 3, 4, 5, 6 };
		AtomicIntegerArray i = new AtomicIntegerArray(nums);
		For (int j = 0; j < nums.length; j++) {
			System.out.println(i.get(j));
		}
		temvalue = i.getAndSet(0, 2);
		System.out.println("temvalue:" + temvalue + "; i:" + i);
		temvalue = i.getAndIncrement(0);
		System.out.println("temvalue:" + temvalue + "; i:" + i);
		temvalue = i.getAndAdd(0, 5);
		System.out.println("temvalue:" + temvalue + "; i:" + i);
	}

}

4 Reference type atom class

4.1 Introduction to Atomic Classes

A primitive type atomic class can only update one variable. If you need an atom to update multiple variables, you need to use a reference type atom class.

  • AtomicReference: reference type atom class
  • AtomicStampedReference: The atomic class of the field in the atomic update reference type
  • AtomicMarkableReference : atomic update reference type with tag bit

The methods provided by the above three classes are almost the same, so we will introduce AtomicReference as an example here.

4.2 AtomicReference class usage example

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceTest {

	public static void main(String[] args) {
		AtomicReference<Person> ar = new AtomicReference<Person>();
		Person person = new Person("SnailClimb", 22);
		ar.set(person);
		Person updatePerson = new Person("Daisy", 20);
		ar.compareAndSet(person, updatePerson);

		System.out.println(ar.get().getName());
		System.out.println(ar.get().getAge());
	}
}

class Person {
	private String name;
	private int age;

	public Person(String name, int age) {
		super();
		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;
	}

}

The above code first creates a Person object, then sets the Person object into the AtomicReference object, and then calls the compareAndSet method, which is to set ar through the CAS operation. If the value of ar is person, set it to updatePerson. The implementation principle is the same as the compareAndSet method in the AtomicInteger class. The output after running the above code is as follows:

Daisy
20

4.3 AtomicStampedReference class usage example

import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicStampedReferenceDemo {
    public static void main(String[] args) {
        // instantiate, take the current value and the stamp value
        final Integer initialRef = 0, initialStamp = 0;
        final AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(initialRef, initialStamp);
        System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());

        // compare and set
        final Integer newReference = 666, newStamp = 999;
        final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp);
        System.out.println("currentValue=" + asr.getReference()
                + ", currentStamp=" + asr.getStamp()
                + ", casResult=" + casResult);

        // get the current value and the current stamp value
        Int[] arr = new int[1];
        final Integer currentValue = asr.get(arr);
        final int currentStamp = arr[0];
        System.out.println("currentValue=" + currentValue + ", currentStamp=" + currentStamp);

        // Set the stamp value separately
        final boolean attemptStampResult = asr.attemptStamp(newReference, 88);
        System.out.println("currentValue=" + asr.getReference()
                + ", currentStamp=" + asr.getStamp()
                + ", attemptStampResult=" + attemptStampResult);

        // Reset the current value and the stamp value
        asr.set(initialRef, initialStamp);
        System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());

        // [Not recommended, unless you understand the meaning of the comment] weak compare and set
        // Confused! weakCompareAndSet This method eventually calls the compareAndSet method. [Version: jdk-8u191]
        // but the comment says "May fail spuriously and does not provide ordering guarantees,
        // so is only rarely an appropriate alternative to compareAndSet."
        //todo feels that it is possible that jvm is forwarded in the native method by method name.
        final boolean wCasResult = asr.weakCompareAndSet(initialRef, newReference, initialStamp, newStamp);
        System.out.println("currentValue=" + asr.getReference()
                + ", currentStamp=" + asr.getStamp()
                + ", wCasResult=" + wCasResult);
    }
}

The output is as follows:

currentValue=0, currentStamp=0
currentValue=666, currentStamp=999, casResult=true
currentValue=666, currentStamp=999
currentValue=666, currentStamp=88, attemptStampResult=true
currentValue=0, currentStamp=0
currentValue=666, currentStamp=999, wCasResult=true

4.4 AtomicMarkableReference class usage example

import java.util.concurrent.atomic.AtomicMarkableReference;

public class AtomicMarkableReferenceDemo {
    public static void main(String[] args) {
        // instantiate, take the current value and the mark value
        final boolean initialRef = null, initialMark = false;
        final AtomicMarkableReference<boolean> amr = new AtomicMarkableReference<>(initialRef, initialMark);
        System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());

        // compare and set
        final boolean newReference1 = true, newMark1 = true;
        final boolean casResult = amr.compareAndSet(initialRef, newReference1, initialMark, newMark1);
        System.out.println("currentValue=" + amr.getReference()
                + ", currentMark=" + amr.isMarked()
                + ", casResult=" + casResult);

        // get the current value and the current mark value
        boolean[] arr = new boolean[1];
        final boolean currentValue = amr.get(arr);
        final boolean currentMark = arr[0];
        System.out.println("currentValue=" + currentValue + ", currentMark=" + currentMark);

        // Set the mark value separately
        final boolean attemptMarkResult = amr.attemptMark(newReference1, false);
        System.out.println("currentValue=" + amr.getReference()
                + ", currentMark=" + amr.isMarked()
                + ", attemptMarkResult=" + attemptMarkResult);

        // Reset the current value and the mark value
        amr.set(initialRef, initialMark);
        System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());

        // [Not recommended, unless you understand the meaning of the comment] weak compare and set
        // Confused! weakCompareAndSet This method eventually calls the compareAndSet method. [Version: jdk-8u191]
        // but the comment says "May fail spuriously and does not provide ordering guarantees,
        // so is only rarely an appropriate alternative to compareAndSet."
        //todo feels that it is possible that jvm is forwarded in the native method by method name.
        final boolean wCasResult = amr.weakCompareAndSet(initialRef, newReference1, initialMark, newMark1);
        System.out.println("currentValue=" + amr.getReference()
                + ", currentMark=" + amr.isMarked()
                + ", wCasResult=" + wCasResult);
    }
}

The output is as follows:

currentValue=null, currentMark=false
currentValue=true, currentMark=true, casResult=true
currentValue=true, currentMark=true
currentValue=true, currentMark=false, attemptMarkResult=true
currentValue=null, currentMark=false
currentValue=true, currentMark=true, wCasResult=true

5 Object’s property modification type atom class

5.1 Object Attribute Modification Type Atomic class Introduction

If you need an atom to update a field in a class, you need to modify the type atom class using the object’s properties.

  • AtomicIntegerFieldUpdater: Updater for atomic update shaping fields
  • AtomicLongFieldUpdater: Updater for atomic update long integer fields
  • AtomicStampedReference: Atomic update reference type with version number. This class associates an integer value with a reference and can be used to resolve the atomic update data and the version number of the data. It can solve the ABA problem that may occur when using atomic update with CAS.

It takes two steps to atomically update the properties of an object. The first step, because the object’s property modification type atom class is an abstract class, so each time you use it must use the static method newUpdater () to create an updater, and you need to set the class and properties you want to update. In the second step, the updated object property must use the public volatile modifier.

The methods provided by the above three classes are almost the same, so we will introduce AtomicIntegerFieldUpdater as an example here.

5.2 AtomicIntegerFieldUpdater class usage example

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterTest {
	public static void main(String[] args) {
		AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");

		User user = new User("Java", 22);
		System.out.println(a.getAndIncrement(user));// 22
		System.out.println(a.get(user));// 23
	}
}

class User {
	private String name;
	public volatile int age;

	public User(String name, int age) {
		super();
		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 results:

twenty two
twenty three

No public

If you want to follow my updated articles and share the dry goods in real time, you can follow my public number.

Last modified October 4, 2020