In Java concurrency, synchronized is probably the first word to use. Synchronized is a heavyweight lock that can cause performance problems in many cases. Volatile is a good choice, but it does not guarantee atomicity and can only be used in certain situations.

Exclusive locks like synchronized are pessimistic locks, which assume that a conflict will occur, and the lock will work, and optimistic locks, which assume that there is no conflict, and I can just do something, and if there is a conflict, I’ll try again until I succeed. The most common type of optimistic lock is CAS.

In addition, I organized free Java architecture learning materials, learning technology content includes: Spring, Dubbo, MyBatis, RPC, source code analysis, high concurrency, high performance, distributed, performance optimization, micro services advanced architecture development and so on.

Friends in need can click:This point! This point!, code word: J j.

When we read the source code for classes under the Concurrent package, we find that CAS is applied internally to both the AQS inside ReenterLock and various atomic-beginning classes, the most common being the i++ situation we encounter in Concurrent programming. The traditional approach would surely be to add the synchronized keyword to the method:

public class Test {

    public volatile int i;

    public synchronized void add(a) { i++; }}Copy the code

However, this method may be poor in performance, and we can also use AtomicInteger to guarantee the ++ of I atoms.

public class Test {

    public AtomicInteger i;

    public void add(a) {
        i.getAndIncrement();
    }
}
Copy the code

Let’s look inside getAndIncrement:

public final int getAndIncrement(a) {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
Copy the code

Further down to getAndAddInt():

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;
}
Copy the code

Here we see the function compareAndSwapInt, which is where the CAS abbreviation came from. So what does this function do?

First of all, if we look at this before compareAndSwapInt, what class does it belong to? Let’s look at getAndAddInt, before unsafe. Here we enter the Unsafe class. A note about the Unsafe class. Combined with AtomicInteger’s definition:

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
    
    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe(a);private static final long valueOffset;
    
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }}private volatile intvalue; .Copy the code

In the AtomicInteger data definition section, we can see that the actual stored value is in value. Besides, we also get the unsafe instance and define valueOffset.

The unsafe objectFieldOffset is called to retrieve the value offset from the Atomic class file. The unsafe objectFieldOffset is also called to retrieve the value offset from the Atomic class file. So valueOffset actually records the offset of value.

The unsafe getIntVolatile(var1, var2) is a native method, and the unsafe getIntVolatile(var1, var2) function is also called. The unsafe getIntVolatile(var1, var2) function is also called. Value at the offset of var2. Var1 is AtomicInteger, var2 is valueOffset we mentioned earlier, so we get the value from memory at valueOffset now.

CompareAndSwapInt (obj, offset, expect, update) compareAndSwapInt (obj, offset, expect, update) If the value in obj is equal to expect, then no other thread has changed the variable, so update it to update it. If the CAS in obj fails, then continue with the spin CAS. In JNI it is done with the help of a CPU instruction. So it’s still atomic.

Basic Principles of CAS

The CAS base layer is implemented using JNI to call C code. If you have Hotspot source, you can find it in unsafe.cpp:

static JNINativeMethod methods_15[] = {
    // omit a bunch of code...
    {CC"compareAndSwapInt",  CC"("OBJ"J""I""I"")Z".FN_PTR(Unsafe_CompareAndSwapInt)},
    {CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z".FN_PTR(Unsafe_CompareAndSwapLong)},
    // omit a bunch of code...
};
Copy the code

We can see that the compareAndSwapInt implementation is in Unsafe_CompareAndSwapInt, and further to Unsafe_CompareAndSwapInt:

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
Copy the code

Atomic:: CMPXCHG (x, addr, e) is called, where parameter x is the value to be updated and parameter e is the original memory value. CMPXCHG code can be seen based on the implementation of various platforms, here I choose Linux X86 platform under the source analysis:

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_MP(a);__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc"."memory");
  return exchange_value;
}
Copy the code

__asm__ indicates ASM assembly and __volatile__ disables compiler optimizations

// Adding a lock prefix to an instruction on MP machine
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1:"
Copy the code

OS ::is_MP determines whether the current system is a multi-core system, if so, the bus is locked, so other processors on the same chip cannot access the memory through the bus temporarily, ensuring the atomicity of the instruction in the multi-processor environment.

Before we dive into the assembly, let’s look at the basic format for embedding assembly:

asm ( assembler template
    : output operands                  /* optional */
    : input operands                   /* optional */
    : list of clobbered registers      /* optional */
    );
Copy the code
  • Template is CMPXCHGL %1, with (%3) representing the assembly template
  • Output operands represents the output operands,=a corresponds to the EAX register
  • Input operand represents the input parameters, %1 is exchange_value, %3 is dest, %4 is MP, r represents any register, a is eAX register
  • Cc indicates that CMPXCHGL will affect the register, and memory tells the compiler to fetch the latest value of the variable from memory, thus achieving the sensation of being volatile.

CMPXCHGL exchange_value,dest, CMPXCHGL exchange_value,dest, compare_value The l at the end of CMPXCHGL indicates that the operand is of length 4, as we already know. CMPXCHGL defaults to comparing the values of the EAX registers, compare_value and Exchange_value. If they are equal, the dest value is assigned to Exchange_value, otherwise exchange_value is assigned to EAX.

Finally, the JDK implements AtomicInteger CAS operation atomicity through CPU’s CMPXCHGL instruction support.

The problem of the CAS

ABA problem

CAS needs to check if the value has changed and update it if it has not. However, if A value is A, then B, and then A, then CAS checks that the value has not changed, but in fact it has changed.

This is the ABA problem with CAS. A common solution is to use version numbers. Append the version number to the variable, increment the version number by one each time the variable is updated, and a-b-A becomes 1A-2B-3a.

A class AtomicStampedReference is currently provided in the JDK atomic package to address ABA issues. The compareAndSet method of this class first checks whether the current reference is equal to the expected reference, and whether the current flag is equal to the expected flag, and if all are equal, sets the reference and flag values to the given updated value atomically.

Long cycle time and high overhead

We said that if the CAS is not successful, it will spin in situ, and spinning for a long time can be very expensive for the CPU to execute.

The last

There are Java core knowledge points + a full set of architect learning materials and video + first-line factory interview gem + resume template can be received + Ali Meituannetease Tencent Xiaomi IQiyi Quick hand bilibilibilii interview questions +Spring source code collection +Java architecture practice ebook.

Friends in need can click:This point! This point!, code word: J j.