Java-CAS-atomic and

CAS (Compare and Swap), i.e., the comparison and replacement, a technology that is often used in concurrent algorithms.

CAS idea is simple: three parameters, a current value of V memory, the expected value of the old A, B values ​​will be updated, if and only if the expected value of the A and V are the same memory value, the memory value is modified to B and return true, or do nothing and returns false.

A concept for CAS atomic. Atomic operations or a series of operations is not be interrupted. And CAS in Java is one way to ensure an atomic operation.

Java1.5 from the beginning, JDK concurrency package provides several classes to support atomic operations, are beginning to Atomic.

Similar volatile can not guarantee atomicity i ++ this operation, CAS can be guaranteed.

 

First, the use of atomic Class

To AtomicInteger, for example, commonly used API:

  • public final int get (): Get the current value
  • public final int getAndSet (int newValue): Get the current value, and set the new value
  • public final int getAndIncrement (): Get the current value and increment
  • public final int getAndDecrement (): Get the current value and decrementing
  • public final int getAndAdd (int delta): Get the current value, plus the expected value

Integer compared to the advantages of multithreading allows variable increment:

Private  volatile  int COUNT = 0 ;
 // To secure execution thread count ++, requires lock 
public  the synchronized  void INCREMENT () { 
    COUNT ++ ; 
} 
public  int getCount () {
     return COUNT; 
}

After using AtomicInteger:

Private of AtomicInteger COUNT = new new of AtomicInteger ();
 public  void INCREMENT () { 
    count.incrementAndGet (); 
} 
// after use of AtomicInteger, does not require locking, thread safety can be achieved 
public  int getCount () {
     return count.get () ; 
}

 

Two, CAS issue

CAS manner as optimistic locking, synchronized to pessimistic locking. Therefore, the use of CAS to solve concurrency issues generally better performance in the case.

But using CAS way there will be a few questions:

1. Long cycle cost big time

If the time is not successful spin CAS, it will bring a very large CPU execution cost.

2. atomic operation can only guarantee a shared variable

When operating the plurality of shared variables, CAS cycle can not guarantee the atomicity operation, this time need locks.

Java 1.5 from the beginning, JDK classes AtomicReference provided to ensure the reference atom between objects.

3.ABA problem

If a value is A, it became a B, he is a A, then use CAS inspection will find its value has not changed, but actually changed.

Solutions that use the version number. In front of the additional variable version number, each time the variable update version number is incremented by 1, then A-> B-> A becomes 1A-> 2B-> 3A.

From the beginning of Java 1.5, JDK provides AtomicStampedReference, AtomicMarkableReference ABA class to solve the problem.

 

Three, CAS achieve

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

public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}

public native int getIntVolatile(Object o, long offset);

public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);

The last call is  Unsafe Method in class, mainly compareAndSwapInt method, namely CAS.

View Unsafe realization of: https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/fea2c7f50ce8/src/share/vm/prims/unsafe.cpp

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

Wherein the core method Atomic :: cmpxchg (x, addr, e), where x is the parameter value will be updated, the parameter e is the value of the original memory, to the memory address addr parameter.

View Atomic realization of: https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/fea2c7f50ce8/src/share/vm/runtime/atomic.cpp

#include "runtime/atomic.inline.hpp"

jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {
  assert(sizeof(jbyte) == 1, "assumption.");
  uintptr_t dest_addr = (uintptr_t)dest;
  uintptr_t offset = dest_addr % sizeof(jint);
  volatile jint* dest_int = (volatile jint*)(dest_addr - offset);
  jint cur = *dest_int;
  jbyte* cur_as_bytes = (jbyte*)(&cur);
  jint new_val = cur;
  jbyte* new_val_as_bytes = (jbyte*)(&new_val);
  new_val_as_bytes[offset] = exchange_value;
  while (cur_as_bytes[offset] == compare_value) {
    jint res = cmpxchg(new_val, dest_int, cur);
    if (res == cur) break;
    cur = res;
    new_val = cur;
    new_val_as_bytes[offset] = exchange_value;
  }
  return cur_as_bytes[offset];
}

View atomic_linux_x86.inline.hpp, different systems, different CPU have different implementations: https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/fea2c7f50ce8/src/share/vm/runtime/atomic.inline .hpp

The Linux x86 realization: https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/fea2c7f50ce8/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp

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;
}

Windows x86-realization: https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/fea2c7f50ce8/src/os_cpu/windows_x86/vm/atomic_windows_x86.inline.hpp

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}

// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0  \
                       __asm je L0      \
                       __asm _emit 0xF0 \
                       __asm L0:

 


https://benjaminwhx.com/2018/05/03/%E3%80%90%E7%BB%86%E8%B0%88Java%E5%B9%B6%E5%8F%91%E3%80%91%E8%B0%88%E8%B0%88CAS/

https://www.cnblogs.com/noKing/p/9094983.html

Guess you like

Origin www.cnblogs.com/jhxxb/p/11533938.html