Multithreaded concurrency is a very important content in Java language, but also a difficult point in Java foundation. It is important because multithreading is frequently used in daily development, and it is difficult because there are so many knowledge points involved in multithreading concurrency that it is not easy to fully master Java concurrency knowledge. For this reason, Java concurrency is one of the most frequently covered topics in Java interviews. This series of articles will take a systematic look at Java concurrency in terms of the Java memory model, volatile keywords, synchronized keywords, ReetrantLock, Atomic concurrency classes, and thread pools. In this series of articles, you will learn more about the use of volatile, the implementation of synchronized, AQS and CLH queue locking, and clearly understand spin locking, bias locking, optimistic locking, pessimistic locking… And so on a dizzying array of concurrent knowledge.

Multi-threaded concurrency series

This time, understand the Java memory model and the volatile keyword once and for all

This time, thoroughly understand the Synchronized keyword in Java

This time, thoroughly understand the Java ReentranLock implementation principle

This time, understand Java thoroughly and send out the Atomic Atomic classes in the package

Understanding the wait and wake up mechanism of Java threads

Understanding the wait and wake up mechanism of Java threads (Part 2)

Java Concurrency series finale: Get to the bottom of how Java thread pools work

The principle of ThreadLocal is simple

This article, the third in the Java concurrency series, covers Atomic concurrency classes and CAS in Java in detail.

The previous two articles delved into the synchronized keyword and ReentranLock, both of which ensure that only one thread operates on a shared variable by synchronizing state during concurrency. In this article, we’ll look at a thread-safe method that is not locked, which is the Atomic Atomic operation class introduced in JDK1.5.

Before I start, I would like to recommend the GitHub repository AndroidNote, which is my study notes and the source of the first draft of my article. This repository contains a lot of Java and Android advancements. Is a systematic and comprehensive Android knowledge base. It is also a valuable interview guide for students preparing for interviews. Welcome to the GitHub warehouse homepage.

First, initial Atomic and send package

Since JDK1.5 Java in Java. Util. Concurrent. The atomic package under the introduction of some atomic related atomic operations class, these classes to avoid using lock to synchronization, which is more convenient and efficient implementation atomic operations. All classes under the atomic package look like this:

All Atomic classes under the Atomic package apply only to a single element, that is, only one basic data type, object, or array can be guaranteed atomicity. Depending on the scope of use, these classes can be divided into four types: atomic update base types, atomic update arrays, atomic update references, and atomic update attributes.

1. Atomic update base types

AtomicInteger, AtomicLong and AtomicBoolean provide atomic update integer type, atomic update long integer type and atomic update Boolean type functions respectively. Here, we use AtomicInteger as an example to learn how to use it.

AtomicInteger provides a number of methods to call, such as:

// get the current value, then add, equivalent to i++
getAndIncrement()
// Get the current value, then decrement, equivalent to I --
getAndDecrement()
// add 1 and return, equivalent to ++ I
incrementAndGet()
// decrement by 1 and return, equivalent to -- I
decrementAndGet()
// Get the current value and add the expected value
getAndAdd(int delta)
// Get the current value and set the new value
int getAndSet(int newValue)

// ...

Copy the code

It is important to note that these methods are atomic operations, and atomicity can be guaranteed under multiple threads. Take the incrementAndGet method for example:

    AtomicInteger atomicInteger = new AtomicInteger();
    
    private int index;

    public void increase(a) throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                index = atomicInteger.incrementAndGet();
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                index = atomicInteger.incrementAndGet();
            }
        }).start();

        Thread.sleep(1000);
        System.out.println("-- -- -- -- --" + index); // Output 20000
    }
Copy the code

The increase method starts two threads and increments index with AtomicInteger, each time producing 20000.

2. Atomic update reference types

Atomic classes of primitive types can update only one variable, and if atomic updates are required for multiple variables, you need to use reference type atomic classes. The atomic classes of reference type include AtomicReference, AtomicStampedReference, and AtomicMarkableReference.

  • AtomicReference refers to an atomic class
  • AtomicStampedReference An atom updates a reference type with a version number. This class associates integer values with references and can be used to resolve atomic update data and the version number of the data, and can solve ABA problems that may occur when atomic updates are made using CAS. (Detailed analysis on CAS and ABA issues will be made later)
  • The AtomicMarkableReference atom updates the reference type with the tag. This class associates a Boolean tag with a reference.

Next, take AtomicReference as an example to analyze, first look at the AtomicReference class structure:

public class AtomicReference<V> implements java.io.Serializable {

    public final V get(a) {
        return value;
    }

    public final void set(V newValue) {
        value = newValue;
    }
    
    public final boolean compareAndSet(V expectedValue, V newValue) {
        return VALUE.compareAndSet(this, expectedValue, newValue);
    }  
    
    / /... Omit the other
}
Copy the code

As you can see, AtomicReference is a generic class that internally sets and updates methods that reference type data. Take the compareAndSet method as an example to see how to use it.

public class Book {
    public String name;

    public int price;

    public Book(String name, int price) {
        this.name = name;
        this.price = price; }}Copy the code
AtomicReference<Book> atomicReference = new AtomicReference<>();
Book book1 = new Book(Romance of The Three Kingdoms.42);
atomicReference.set(book1);
Book book2 = new Book("Water Margin".40);
atomicReference.compareAndSet(book1, book2);
System.out.println("Book name is " + atomicReference.get().name + "The price is." + atomicReference.get().price);           
        
Copy the code

The output is:

Book name is water Margin, the price is 40

The code above first associates Book1 with the AtomicReference and then instantiates Book2. Call the compareAndSet method, passing in book1 and book2, to update the book through CAS. First determine if book1 is expected, if so update to book2. Otherwise continue spinning until the update succeeds.

3. Atomic update array

Here the atom updates the array not on the atoms of the array itself, but on the elements of the array. It mainly includes three classes: AtomicIntegerArray, AtomicLongArray and AtomicReferenceArray, respectively representing the element of the atomic update integer array, the element of the atomic update long integer array and the element of the atomic update reference type array. Let’s take AtomicIntegerArray as an example:

public class AtomicIntegerArray implements java.io.Serializable {
    // Final int array
    private final int[] array;
    // Get the ith element of the array
    public final int get(int i) {
        return (int)AA.getVolatile(array, i);
    }   
    // Sets the ith element of the array
    public final void set(int i, int newValue) {
        AA.setVolatile(array, i, newValue);
    }
    // CAS changes the I element
    public final boolean compareAndSet(int i, int expectedValue, int newValue) {
        return AA.compareAndSet(array, i, expectedValue, newValue);
    }
    // Get the ith element and increment by 1
    public final int getAndIncrement(int i) {
        return (int)AA.getAndAdd(array, i, 1);
    }
    // Get the ith element and subtract 1
    public final int getAndDecrement(int i) {
        return (int)AA.getAndAdd(array, i, -1);
    }   
    // Add 1 to the ith element of the array
    public final int incrementAndGet(int i) {
        return (int)AA.getAndAdd(array, i, 1) + 1;
    }  
    // Subtract 1 from the ith element of the array
    public final int decrementAndGet(int i) {
        return (int)AA.getAndAdd(array, i, -1) - 1;
    }    
    / /... omit
}    
Copy the code

As you can see, a final-decorated int array is maintained inside the AtomicIntegerArray, and all operations in the class are operations on array elements. At the same time, these methods are atomic operations, which can ensure the security of data under multithreading.

4. Atomically update object properties

If you update a field in an object directly, you can use the atomic class of the update object field. Consists of three classes, AtomicIntegerFieldUpdater, AtomicLongFieldUpdater and AtomicReferenceFieldUpdater. It is important to note that these classes can only be used if the following conditions are met.

  • The field to be operated on cannot be static;
  • The field being manipulated cannot be final;
  • Fields to be operated on must be volatile;
  • Property must be visible to the region of the current Updater.

Below AtomicIntegerFieldUpdater, for example, combined with the precedent of the Book class to update the price of the Book, pay attention to the price with volatile.

public class Book {
    public String name;

    public volatile int price;

    public Book(String name, int price) {
        this.name = name;
        this.price = price; }}Copy the code
AtomicIntegerFieldUpdater<Book> updater = AtomicIntegerFieldUpdater.newUpdater(Book.class, "price");
Book book = new Book(Romance of The Three Kingdoms.42);
updater.set(book, 50);
System.out.println( "The updated price is." + updater.get(book));
Copy the code

The following output is displayed:

The updated price is 50

Instantiate a Book, the price for 42, through AtomicIntegerFieldUpdater can amend the price to 50.

Second, the CAS

As mentioned in the previous article, classes under Atomic packages are lock-free, which is achieved thanks to CAS. The concept of CAS has been covered in previous articles. So let’s take a closer look at what CAS is.

CAS is short for Compare And Swap. CAS is a lock-free algorithm with the following algorithm ideas:

CAS: compareAndSwap(V,E,N); Where V represents the variable to be updated, E represents the expected value, and N represents the expected value to be updated. Call compareAndSwap to update the variable V. If V is equal to the expected value E, update it to N. If V is not equal to the expected value E, another thread is updating the variable, and no update will be performed. Instead, re-read the value of the variable and try calling compareAndSwap again to update it.

It can be seen that CAS actually has a circular process. If multiple threads are modifying the variable V at the same time, they will get the value of the variable before modifying it, and then compare it with the variable to see if it is equal. If it is equal, it means that no other threads are modifying the variable, so they can update the variable themselves. If the variable to be modified is found to be different from the expected value, it indicates that another thread has modified variable V after the value of variable V is read again. Then, the update is abandoned, the value of variable V is read again, and the modification is attempted again until the modification is successful. This loop is also known as spin, and the entire CAS operation is shown below:

2. Disadvantages of CAS

Although lock-free synchronization can be achieved through CAS, CAS has its limitations and problems.

  • (1) Atomicity of only one shared variable can be guaranteed

CAS does not guarantee the synchronization of a piece of code with multiple variables as synchronized and RetranLock do. The CAS is not guaranteed for multiple shared variable manipulations and must be implemented using shackles.

  • (2) There is a performance overhead problem

Since CAS is a spin operation, unsuccessful CAS for a long period of time can incur significant CPU overhead.

  • (3) ABA problems

Since CAS guarantees atomicity by checking whether the value has changed, if A variable V is A, thread 1 and thread 2 both read the variable A at the same time, thread 1 changes the value of V to B and then back to A, and thread 2 never grabs the CPU time slice. Thread 2 does not execute until thread 1 changes the value of V back to A. So at this point, thread 2 doesn’t know that V has changed. This problem is known as the ABA problem.

The ABA problem can be solved easily by adding a version number, changing the value of the next update and updating the version number. One atom stampedreference is designed to solve ABA problems.

3.CPU support for CAS

CAS is a system primitive in the operating system. The primitive consists of multiple instructions and the execution of the primitive is continuous and uninterruptible. So CAS is actually an atomic instruction of the CPU, and although CAS appears to be a comparison-and-swap operation, it is actually the CPU that guarantees the atomic operation.

4.CAS and Atomic atoms

With CAS in mind, let’s take a look at how the Atomic classes in the Atomic package implement Atomic operations using CAS. Let’s take AtomicInteger as an example.

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
}

    public final int getAndSet(int newValue) {
        return U.getAndSetInt(this, VALUE, newValue);
    }
    public final int getAndIncrement(a) {
        return U.getAndAddInt(this, VALUE, 1);
    }

    public final int getAndDecrement(a) {
        return U.getAndAddInt(this, VALUE, -1);
    }
 
    public final int getAndAdd(int delta) {
        return U.getAndAddInt(this, VALUE, delta);
    }

    public final int incrementAndGet(a) {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }

    public final int decrementAndGet(a) {
        return U.getAndAddInt(this, VALUE, -1) - 1;
    }

Copy the code

As you can see in the AtomicInteger class, all operations are performed through a member variable of type Unsafe. The Unsafe class is a class under the Sun.misc package that provides low-level, insecure operation methods, including CAS capabilities.

Unsafe is the CAS implementation class

Unsafe is a fantastic and little-known Java class because it is rarely used in development. However, this class provides a lot of functionality for us. It allows the Java language to manipulate memory like C language Pointers. It also provides CAS, memory barriers, thread scheduling, object manipulation, array manipulation, and so on, as shown in the following figure.

Here’s a quick look at the Unsafe class.

1. Obtain the Unsafe instance

The Unsafe class is a singleton and provides a getUnsafe method to retrieve an instance of Unsafe. However, this method is only valid if the Unsafe class is loaded by the bootstrap class loader. Otherwise, a SecurityException is thrown, as follows:

Exception in thread "main" java.lang.SecurityException: Unsafe
	at jdk.unsupported/sun.misc.Unsafe.getUnsafe(Unsafe.java:99)
	at atomic.AtomicDemo.increase(AtomicDemo.java:28)
	at atomic.AtomicDemo.main(AtomicDemo.java:34)
Copy the code

Therefore, getting an instance of the Unsafe class requires a different approach. Retrieving an Unsafe instance using reflection is a good alternative, as follows:

        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Unsafe unsafe = (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }

Copy the code

2. CAS in the Unsafe class

The following are the main CAS methods in the Unsafe class

    // The first argument o is the given object, offset is the memory offset of the object, by which the field is quickly located and the value of the field is set or obtained. Expected indicates the expected value, and x indicates the value to be set. The following three methods all operate via the CAS atomic instruction.
    public final native boolean compareAndSetInt(Object o,long offset,int expected,int x);
                                                 
    public final native boolean compareAndSetObject(Object o, long offset,Object expected,Object x);    
    
    public final native boolean compareAndSetLong(Object o, long offset,long expected,long x);    
Copy the code

As you can see, these methods are native methods, the underlying code implementation of the call. Methods getAndAddInt, getAndAddLong, getAndSetInt, getAndSetLong, and getAndSetObject have been introduced in JDK1.8 to support different types of CAS operations.

The CAS operation is implemented in AtomicInteger using this method.

3. Thread scheduling

Unsafe provides methods for thread suspension, recovery, and locking mechanisms.

// Unblock the thread
public native void unpark(Object thread);
// Block the thread
public native void park(boolean isAbsolute, long time);
// Get the object lock (reentrant lock)
@Deprecated
public native void monitorEnter(Object o);
// Release the object lock
@Deprecated
public native void monitorExit(Object o);
// Try to obtain the object lock
@Deprecated
public native boolean tryMonitorEnter(Object o);
Copy the code

In the previous article on RetranLock and AQS, the operation involving thread suspension was also called the Unsafe park method.

    // LockSupport
    
    private static final Unsafe U = Unsafe.getUnsafe();
    
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        U.park(false.0L);
        setBlocker(t, null);
    }
Copy the code

### Unsafe also provides methods for instantiating objects and manipulating object attributes

// Returns the offset of the memory address of an object member attribute relative to the memory address of the object
public native long objectFieldOffset(Field f);
GetInt, getDouble, getLong, getChar, etc
public native Object getObject(Object o, long offset);
PutInt, putDouble, putLong, putChar, etc
public native void putObject(Object o, long offset, Object x);
// Get a reference to a variable from the specified offset of the object, using the loading semantics of volatile
public native Object getObjectVolatile(Object o, long offset);
// Store references to variables at the specified offset of the object, using the storage semantics of volatile
public native void putObjectVolatile(Object o, long offset, Object x);
// The ordered, delayed version of putObjectVolatile does not guarantee that a value change is immediately visible to other threads. Only valid if the field is modified by the volatile modifier
public native void putOrderedObject(Object o, long offset, Object x);
// Bypass the constructor and initialize the code to create the object
public native Object allocateInstance(Class
        cls) throws InstantiationException;
Copy the code

The allocateInstance method in Unsafe, which bypasses object constructors to create objects directly, was used by Gson when parsing JSON deserialized objects.

/ / from Gson# UnsafeAllocator
public abstract <T> T newInstance(Class<T> var1) throws Exception;

public static UnsafeAllocator create(a) {
        try{ Class<? > unsafeClass = Class.forName("sun.misc.Unsafe");
            Field f = unsafeClass.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            final Object unsafe = f.get((Object)null);
            final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
            return new UnsafeAllocator() {
                public <T> T newInstance(Class<T> c) throws Exception {
                    assertInstantiable(c);
                    returnallocateInstance.invoke(unsafe, c); }}; }catch (Exception var6) {
            / /... Omit exception handling}}Copy the code

For details on how Gson instantiates objects using allocateInstance, see “Null Pointers raised by a Non-static inner Class.”

5.### Unsafe other features

In addition to CAS, thread scheduling, and object-related functions, Unsafe also provides memory operations to allocate out-of-heap memory. Provides array-related methods to locate each element in an array in memory, etc… Since it is not the focus of this article, I will not introduce it here. Those who are interested can look it up by themselves.

Reference & Recommended reading

Java magic classes: Unsafe Application parsing

Java Concurrent Programming – Lock-free CAS with Unsafe classes and their parallel Atomic packages