One year's work experience, I was asked about CAS

Wedge

In the previous article, we introduced synchronized and learned that synchronized can solve the atomicity problem of some data. In this article, we continue to learn the knowledge of CAS non locking with AtomicInteger as the starting point.

Using synchronized to solve the atomicity of i + +

Use the synchronized keyword to ensure data atomicity

public class Test {

   static int synchronizedValue = 0;
   
   public static void main(String[] args) {
      // Using synchronized++
      synchronizedAdd();
   }
   
   private static void synchronizedAdd() {
      for(int i = 0; i < 50; i++) {
         new Thread(() -> {
            synchronized(Test.class) {
               System.out.println("synchronizedAdd: "+ ++Test.synchronizedValue);;
            }
         }).start();
      }
   }
    
}

result:

synchronizedAdd: 50

Solving self increasing atomicity using AtomicInteger

static AtomicInteger atomicValue = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {
   // Using atomic++
   atomicAdd();
}
private static void atomicAdd() {
   for(int i = 0; i < 50; i++) {
      new Thread(() -> System.out.println("atomicAdd: "+ Test.atomicValue.incrementAndGet())).start();
   }
}

result

atomicAdd: 50

CAS principle

What is CAS? The full English name is compare and swap, which means comparison and exchange when translated into Chinese. The professional term is optimistic lock. In other words, when we modify the data, we will try to compare whether this value has been modified by others. If it has not been modified, we can modify it ourselves; If it has been modified, we will retrieve the latest value and repeat the above steps to compare again. Next, we further analyze CAS from AtomicInteger source code.

AtomicInteger source code analysis

Looking at the source code of AtomicInteger, we find that it can be divided into several parts.

  1. Unsafe: core class, which is really responsible for executing CAS operations
  2. Value and valueOffset: value and offset
  3. API interface: various usage modes are provided externally, mainly encapsulating some Unsafe operations

Unsafe

Unsafe, as its name implies, is an unsafe class with a large number of native methods. In principle, JDK does not allow us to use it. It is a class for internal use in JDK. First of all, his constructor is private and cannot be instantiated manually. Second, although he provides unsafe Getunsafe () method to obtain an instance, but it has a judgment. If it is not loaded by the system, it will directly throw an exception. The source code mentioned above is as follows:

// (1) Construction method privatization 
private Unsafe() {
}

public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    // (2) If Unsafe is not loaded by the JVM, an error will be reported
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

valueOffset

When the class is initialized, a static code block will be loaded, and the offset of a final tag will be determined through unsafe. The source code is as follows

private static final long valueOffset;

// When the class is initialized, the static code block will be executed to determine the offset of a final tag through unsafe
static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

Then we return to the method atomicAdd we wrote ourselves

private static void atomicAdd() {
    for(int i = 0; i < 50; i++) {
        new Thread(() -> System.out.println("atomicAdd: "+ Test.atomicValue.incrementAndGet())).start();
    }
}

Follow up the incrementAndGet method, which is a wrapper method that directly calls unsafe getAndAddInt

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

Follow up unsafe Getandaddint method, OK, the following is the code we need to analyze

public final int getAndAddInt(Object Current object, long Offset, int Self increment) {
    int Object value;
    do {
        // The purpose of this local method is,
        // From the AtomicInteger object instance, obtain the position of the value field according to the valueOffset offset offset, so as to obtain the value of the current value
        Object value/Calculated object value = this.getIntVolatile(Current object, Offset);
    } 
    // If the [calculated value] is inconsistent with the [current object + offset], the compare will be false this time, and the next cycle will be entered
    // If the [calculated value] is consistent with the [current object + offset], then compare is true this time. At this time
    // [value of object] = [value of calculated object] + [self increment], and jump out of the loop
    while(!this.compareAndSwapInt(Current object, Offset, Calculated object value, Calculated object value + Self increment));

    return Object value;
}

Some common problems of Atomic CAS

ABA problem

For example, you only operate when A value is A. Therefore, the following situations may occur

  1. Initially A
  2. A->B
  3. B->A
  4. Start operation
    (2) In step (3), the value has been changed, but the value is the same as what I expected. Therefore, when we go to compareAndSwapInt, we will find that the value is still A and the setting is successful.

Note: how to solve ABA problem? Just add a time stamp. Bring a time stamp when comparing.

Infinite cycle problem

Look at the source code, because we use the do... while loop, it may cycle many times, but it is not successful.

Note: how to solve the infinite loop problem? jdk provides us with an idea to segment CAS. Interested readers can refer to the LongAdder class.

Custom object atomic problem

AtomicInteger can only guarantee the atomicity of a variable.

Note: what about complex objects? jdk provides us with AtomicReference, which compares whether the reference of this object is a reference.

Keywords: Java JavaEE Spring Concurrent Programming

Added by twopeak on Thu, 27 Jan 2022 16:48:20 +0200