Detailed explanation of Atomic class and Unsafe magic class of concurrent programming

Keywords: Java Concurrent Programming

Catalogue of series articles

1: Computer model & detailed explanation of volatile keyword
2: Lock system in java
3: synchronized keyword explanation
5: Atomic atomic class and Unsafe magic class

preface

It can be seen from the previous article. volatile keyword and synchronize keyword can help us solve the problem of data security in concurrent programming. In the jdk's own package, some atomic class operations are also provided.

1, What is atomic operation?

atom originally means "the smallest particle that cannot be further divided", while atomic operation means "one or a series of operations that cannot be interrupted". Implementing atomic operations on multiprocessors becomes a bit complex. In this article, let's talk about how to implement atomic operations in Inter processor and Java.

2: Unsafe magic

Unsafe is a class under sun.misc package. It mainly provides some methods for performing low-level and unsafe operations, such as direct access to system memory resources, self-management of memory resources, etc. these methods have played a great role in improving Java operation efficiency and enhancing the operation ability of Java language underlying resources. However, the unsafe class enables the Java language to operate the memory space like the C language pointer, which undoubtedly increases the risk of pointer related problems in the program. Excessive and incorrect use of unsafe classes in programs will increase the probability of program errors and make Java, a safe language, no longer "safe". Therefore, the use of unsafe must be cautious. The unsafe class is a singleton implementation that provides a static method getUnsafe to obtain the unsafe instance. It is valid only when and only when the class calling the getUnsafe method is loaded by the boot class loader. Otherwise, a SecurityException exception is thrown.

1. How to get Unsafe instances?

  1. Starting from the use restrictions of getUnsafe method, add the jar package path of class a calling Unsafe related methods to the default bootstrap path through the Java command line command - Xbootclasspath/a, so that a is loaded by the boot class loader, so as to safely obtain Unsafe instances through Unsafe.getUnsafe method.
    java ­Xbootclasspath/a:${path}
    Where path is the jar package path of the class that calls the Unsafe related method

  2. Get the singleton Unsafe through reflection

public class UnsafeInstance { 
   public static Unsafe reflectGetUnsafe() { 
   try {
 Field field =  Unsafe.class.getDeclaredField("theUnsafe");                                 	
 field.setAccessible(true); 
  return (Unsafe) field.get(null); 
    } catch (Exception e) {
            e.printStackTrace(); 
            }
   return null;
 } 
 }

2. Unsafe function introduction

The API s provided by Unsafe can be roughly divided into memory operation, CAS, Class related, object operation, thread scheduling, system information acquisition, memory barrier, array operation, etc

2.1. Memory operation

//Allocate memory, equivalent to malloc function of C + + 
public native long allocateMemory(long bytes);
 //Extended memory 
public native long reallocateMemory(long address, long bytes); //Free memory 
public native void freeMemory(long address);
//Sets a value in a given memory block
public native void setMemory(Object o, long offset, long bytes, byte value); 
 //Memory Copy  
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes); 
 //Gets the given address value, ignoring the access restrictions of the modifier qualifier. Similar operations include: getInt, getDouble, getLong, getChar, etc 
public native Object getObject(Object o, long offset); 
 //Set a value for a given address and ignore the access restrictions of the modifier qualifier. Similar operations include putInt,putDouble, putLong, putChar, etc
public native void putObject(Object o, long offset, Object x);
public native byte getByte(long address); 
   //Set the value of byte type for the given address (the result of this method is determined if and only if the memory address is allocated by allocateMemory)
 public native void putByte(long address, byte x);

Generally, the objects we create in Java are in heap memory. Heap memory is java process memory controlled by the JVM, and they follow the memory management mechanism of the JVM. The JVM will uniformly manage heap memory by using garbage collection mechanism. In contrast, out of heap memory exists in memory areas outside the control of the JVM. The operation of out of heap memory in Java depends on the native method for operating out of heap memory provided by Unsafe.
Reasons for using off heap memory

  • Improvement of garbage collection pause. Because the off heap memory is directly managed by the operating system rather than the JVM, when we use off heap memory, we can maintain a small scale of on heap memory. So as to reduce the impact of recovery pause on the application during GC.
  • Improve the performance of program I/O operations. Generally, in the process of I/O communication, there will be data copying from in heap memory to out of heap memory. It is recommended to store the temporary data that needs frequent inter memory data copying and has a short life cycle in out of heap memory.

2.2 CAS related

As shown in the following source code interpretation, this part mainly refers to the methods of CAS related operations.

/*** CAS
 - @param o Contains the object to modify the field 
 - @param offset The offset of a field in the object 
 - @param expected expected value
 - @param update Update value 
 - @return true | false 
 - */
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);

In the implementation of AtomicInteger, the static field valueOffset is the memory offset address of the field value. When AtomicInteger is initialized, the value of valueOffset is obtained in the static code block through the objectFieldOffset method of Unsafe. In the thread safe method provided in AtomicInteger, you can locate the memory address of value in the AtomicInteger object through the value of the field valueOffset, so you can realize the atomic operation on the value field according to CAS.

2.3 thread scheduling

Including thread suspension, recovery, locking mechanism and other methods.

//Unblock thread 
public native void unpark(Object thread); 
//Blocking thread 
public native void park(boolean isAbsolute, long time); 
//Get object lock (reentrant lock) 
@Deprecated 
public native void monitorEnter(Object o); 
//Release object lock 
@Deprecated 
public native void monitorExit(Object o); 
//Attempt to acquire object lock 
@Deprecated 
public native boolean tryMonitorEnter(Object o);

The methods Park and unpark can be used to suspend and recover a thread. Suspending a thread is realized through the park method. After calling the park method, the thread will be blocked until timeout or interrupt conditions occur; Unpark can terminate a suspended thread and restore it to normal.
AbstractQueuedSynchronizer, the core class of the Java lock and synchronizer framework, blocks and wakes up threads by calling LockSupport.park() and LockSupport.unpark(). The park and unpark methods of LockSupport are actually implemented by calling the park and unpark methods of Unsafe.

2.4 memory barrier

It is introduced in Java 8 to define the memory barrier (also known as memory barrier, memory barrier, barrier instruction, etc. it is a kind of synchronization barrier instruction, which is a synchronization point in the operation of random access to memory by CPU or compiler, so that all read and write operations before this point can be executed before the operation after this point can be started), so as to avoid code reordering.

//Memory barrier that prevents the load operation from reordering. Load operations before the barrier cannot be reordered behind the barrier, and load operations after the barrier cannot be reordered before the barrier 
public native void loadFence(); 
//Memory barrier to prevent store operation reordering. Store operations before the barrier cannot be reordered behind the barrier, and store operations after the barrier cannot be reordered before the barrier 
public native void storeFence(); 
//Memory barrier, which prohibits the reordering of load and store operations 
public native void fullFence();

2: Atomic classes provided by jdk

In the Atomic package, there are 12 classes and four Atomic update methods, namely, Atomic update basic type, Atomic update array, Atomic update reference and Atomic update field. The classes in the Atomic package are basically wrapper classes implemented using Unsafe.

  • Basic classes: AtomicInteger, AtomicLong, AtomicBoolean
  • Array types: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
  • Reference types: AtomicReference, atomicstampedrence, AtomicMarkableReference
  • Attribute atomic modifier (Updater): AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater

1. Basic class

These three class atoms store a variable value internally, and the visibility of data is guaranteed through volatile keyword modification. Then, it relies on Unsafe CAS method to ensure the correctness of concurrent modification, and then comprehensively uses volatile and Unsafe CAS method to return results, and uses loop to ensure thread safety and certain success of update.

 public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger();
        System.out.println("If the comparison value is consistent with the expected value, it will be updated ");
        atomicInteger.set(11);
        atomicInteger.compareAndSet(12,14);
        System.out.println(atomicInteger.get());
        
        System.out.println("Set a new value and return the latest value ");
        atomicInteger.set(11);
        System.out.println(atomicInteger.decrementAndGet());

        System.out.println("Get the value first and set the new value ");
        atomicInteger.set(11);
        System.out.println(atomicInteger.getAndSet(15));
        System.out.println(atomicInteger.get());

        System.out.println("Add first and return the added value");
        atomicInteger.set(11);
        System.out.println(atomicInteger.incrementAndGet());
        System.out.println(atomicInteger.get());

        System.out.println("Return the value first, and then add a new value");
        atomicInteger.set(11);
        System.out.println(atomicInteger.getAndIncrement());
        System.out.println(atomicInteger.get());

        System.out.println("Custom calculation");
        atomicInteger.set(11);
        IntBinaryOperator test1 = (a, b) -> a * b;
 System.out.println(atomicInteger.accumulateAndGet(2,test1));
    }

2. Array type atomic class

The array type atomic class also has three AtomicIntegerArray, AtomicLongArray and AtomicReferenceArray. AtomicIntegerArray and AtomicLongArray actually have the same functions as AtomicInteger and AtomicLong, but they provide the atomicity of an array, that is, they provide the atomicity of a series of variables, AtomicInteger and AtomicLong maintain the atomicity of a variable.
The comparison between AtomicReferenceArray and AtomicReference is the same as that between AtomicIntegerArray and AtomicInteger.
The difference between them and a single atomic class is that an array will be initialized during reinitialization. Each modification is to modify a data of the specified array index. Reverse thinking means that they are maintaining the atomicity of an array, while the basic atomic class is maintaining the atomicity of a single variable.

3. Reference type atomic class

Atomic classes of reference types also have three atomicreferences, AtomicStampedReference and AtomicMarkableReference. Mainly introduced by AtomicReference, the other two atomic classes are extensions of AtomicReference, but in fact, AtomicReference is the same as the basic atomic class, and I even think it can be classified as the basic class, Because like the basic class, it maintains a variable value, but the type is generic, that is, any type can be saved, and then the reference of value is compared to see whether it is consistent each time it is modified. Therefore, AtomicReference is equivalent to the extension of the basic type. It maintains all objects. It needs to verify the reference of value every time it is modified, and the basic type is to verify the specific value.

AtomicStampedReference is an upgrade of AtomicReference. Its implementation is to maintain a pair type variable modified by volatile. Pair stores the object to be maintained and an int type tag (just like the version number). Each update creates a new pair object. All modifications can be successful only if the object and tag are satisfied at the same time, Solve the ABA problem of CAS.
AtomicMarkableReference is similar to AtomicStampedReference, except that the representation maintained in the Pair class is boolean, which can be used to indicate that the object has been deleted.

  public static void main(String[] args) {
        AtomicReference atomicReference = new AtomicReference();
        String str = "HELLO";
        atomicReference.set(str);
        System.out.println(atomicReference.accumulateAndGet("WORLD", (x, y) -> x + "__AAA__" + y));
    }

4. Attribute atomic modifier

Some objects have been published and cannot be modified, but the thread safety problem may not be considered in the initial design. To ensure the thread safety of some properties, you can use the field Updater. There are three main classes: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater and AtomicReferenceFieldUpdater, The main functions provided are the same as AtomicInteger, AtomicLong and AtomicReference, except that AtomicIntegerFieldUpdater is thread safe for maintaining the specified attribute of a specified object, and AtomicInteger is thread safe for maintaining its own value

    static AtomicIntegerFieldUpdater aifu = AtomicIntegerFieldUpdater.newUpdater(Student.class,"old");
    public static void main(String[] args) {
        Student stu = new Student("Xiao Ming",18);
        System.out.println(aifu.getAndIncrement(stu));
        System.out.println(aifu.get(stu));
    }
    @Data
    static class Student{
        private String name;
        public volatile int old;

        public Student(String name ,int old){
            this.name = name;
            this.old = old;
        }
    }

Posted by iambradn on Thu, 14 Oct 2021 11:14:46 -0700