Java CAS principle analysis

Disclaimer: This article is a blogger original article, follow the CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement.
This link: https://blog.csdn.net/guizaian/article/details/100587131

In Java Concurrency in our initial contact should be the synchronizedkey, but synchronizedbelongs to the heavyweights lock, often causes performance problems, volatileis also a good choice, but volatilecan not guarantee atomicity, can only be used in some cases.

 

Like synchronizedthis exclusive lock belongs pessimistic lock , it is surely on the assumption that conflict, then the lock just useful addition, there are optimistic locking , optimistic locking meaning is the assumption that there is no conflict, then I can just be an action, if if the conflict does, I'll retry until successful, optimistic locking is the most common CAS.

When we read the source category under the Concurrent package, I found that both the atom inside a class ReenterLock AQS, or a variety of Atomic beginning , are applied to the interior CAS, the most common is that we encountered in concurrent programming i++situation. The traditional method is certainly on the plus method synchronizedKeywords:

public class Test {

    public volatile int i;

    public synchronized void add() {
        i++;
    }
}
复制代码

However, this method might almost, we can also use in performance AtomicInteger, you can ensure iatoms ++of.

public class Test {

    public AtomicInteger i;

    public void add() {
        i.getAndIncrement();
    }
}
复制代码

We look getAndIncrementinside:

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
复制代码

And then deep into the 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;
}
复制代码

Here we see compareAndSwapIntthis function, it is also the CASabbreviation of the origin. So careful analysis of this function to do anything at all?

First, we found that compareAndSwapIntthe previous this, then it belongs to which category it, we spotted a step getAndAddInt, in front unsafe. Here we enter the Unsafeclass. Here we must to Unsafebe a description of the class. Combined with AtomicIntegerthe definition of terms:

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();
    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;
    ...
复制代码

In the AtomicIntegerportion of the data definition, we can see that, in fact, the actual value is stored in valuethe, in addition we also get the unsafeinstance, and defines valueOffset. To see staticthe block, to understand the class loading process knows staticwhen loading blocks occur in class loading, it is initialized first, we call this time unsafeof objectFieldOffsetthe Atomicclass file to obtain valuethe offset, then valueOffsetin fact recorded valuebiased shift amount.

Back to the top of a function getAndAddInt, we look at var5what is obtained by calling unsafethe getIntVolatile(var1, var2), this is a native method, to achieve specific source JDK go looked, in fact, get var1, the var2value at offset. var1That is AtomicInteger, var2that we mentioned earlier valueOffset, so that we get from memory to the current valueOffsetvalue at the.

Now focus here, compareAndSwapInt(var1, var2, var5, var5 + var4)in fact, replaced by compareAndSwapInt(obj, offset, expect, update)more clearly, meaning that if the objinside valueand expectequal, as evidenced by no other thread changed this variable, then update it as updateif this step is CASnot successful, then continue using the spin of the way for CASoperation, which is two steps removed first glance, ah, in fact, JNIthere is a means of the CPUinstruction is completed. They still atomic operation.

The underlying principle of CAS

CAS underlying use JNIto call C code to achieve, if you have Hotspotthe source code, then Unsafe.cppyou can find in its implementation:

static JNINativeMethod methods_15[] = {
    //省略一堆代码...
    {CC"compareAndSwapInt",  CC"("OBJ"J""I""I"")Z",      FN_PTR(Unsafe_CompareAndSwapInt)},
    {CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z",      FN_PTR(Unsafe_CompareAndSwapLong)},
    //省略一堆代码...
};
复制代码

We can see compareAndSwapInt achieved in the Unsafe_CompareAndSwapIntinside, and then down 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
复制代码

p is an object taken, p addr is the address of the offset at the last call Atomic::cmpxchg(x, addr, e), where x is the parameter value will be updated, the parameter e is the value of the original memory. Code can be seen cmpxchg have achieved various platforms based on where I choose to source code in Linux X86 platform analysis:

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_MP();
  __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;
}
复制代码

This is a small compilation of __asm__note is a compilation ASM, __volatile__prohibit compiler optimizations

// Adding a lock prefix to an instruction on MP machine
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
复制代码

os::is_MPDetermining whether the current system is a multi-core system, if the bus is locked give, so other processors on the same chip temporarily can not access the memory bus, the instruction is guaranteed atomicity in a multiprocessor environment.

Before the official interpretation of this compilation, we embed compiled under the basic format to understand:

asm ( assembler template
    : output operands                  /* optional */
    : input operands                   /* optional */
    : list of clobbered registers      /* optional */
    );
复制代码
  • template that cmpxchgl %1,(%3)represents a compilation template

  • output operands represent output operand, =acorresponding to the eax register

  • input operand represents the input parameters, %1that is exchange_value, %3is dest, %4that is mp, rrepresents any register, aor eaxregister

  • list of clobbered registers is some additional parameters ccthat the compiler cmpxchglexecution will affect the flag register memorytells the compiler to the latest value of the variable to be re-read from the memory, which points to achieve a volatilefeeling.

Then the expression in fact cmpxchgl exchange_value ,dest, we find %2that is compare_valuenot to spend, here is to analyze cmpxchglthe semantics of. cmpxchglEnd lrepresents the length of the operand 4, the above has been known. cmpxchglThe default will compare eaxthe value of the register that is, compare_valueand exchange_valuevalues, if they are equal, put the destvalue assigned to exchange_value, otherwise, will be exchange_valueassigned toeax . Specific assembly instructions can view the Intel manual CMPXCHG

Finally, by the CPU of the JDK cmpxchglsupports instructions, to achieve AtomicIntegerthe CASatomic operation.

CAS issues

  1. ABA problem

CAS needs to have time to check the operating values lower value has not changed, if not changed is updated, but if a value is A, became a B, he is a A, you'll find it when using CAS inspection the value does not change, but actually changed. This is the ABA problem CAS. Common Solutions is a version number. Append the version number in front of variables, each variable is updated when the version number plus one, then A-B-Ait will become 1A-2B-3A. Currently it offers a class in the JDK atomic package AtomicStampedReferenceto solve the ABA problem. The method of action of this class is compareAndSet checks the current reference is equal to the expected reference and the current mark is equal to the expected flag, if all equal Atomically the reference value and the flag is set to the given updated value.

  1. Large overhead long cycle time

Above we said that if CAS is not successful, it will spin in place, if a long spin will bring a very large CPU execution cost.


Author: Kabbalah Tree
link: https: //juejin.im/post/5a73cbbff265da4e807783f5
Source: Nuggets
copyright reserved by the authors. Commercial reprint please contact the author authorized, non-commercial reprint please indicate the source.

Guess you like

Origin blog.csdn.net/guizaian/article/details/100587131