[CAS] CAS principle

1 CAS principle

CAS is the underlying principle of all atomic classes, and optimistic locking mainly uses the CAS algorithm.

CAS, compare and exchange, isThe non-blocking atomic operation provided by the JDK guarantees the atomicity of comparison-update operations through hardware. Usually combined with volatile to ensure the atomicity of shared variables.

Idea: Get the latest value A (expected value) of the current variable, and then perform the CAS operation. At this time, if the value V of the variable in the memory (memory value V) is equal to the expected value A, it means that it has not been modified by other threads, so I will change the variable value to B (updated value); if it is not A, no more modification.

CAS operations use special instructions of the CPU, and the CPU guarantees atomicity to complete a series of operations without security issues.

CAS variables need to be modified with volatile to ensure visibility between threads.

Use scenarios of CAS algorithm ideas

  • Optimistic lock
  • Concurrent container, such as ConcurrentHashMap
  • Atomic class

2 Analysis of CAS usage in AtomicLong

// 获取Unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 获取变量value在内存中的偏移量
private static final long valueOffset;
static {
    
    
    try {
    
    
        valueOffset = unsafe.objectFieldOffset
            (AtomicLong.class.getDeclaredField("value"));
    } catch (Exception ex) {
    
     throw new Error(ex); }
}

During the class loading process, the Unsafe instance and the valueOffset offset are first loaded. valueOffset represents the offset address of the variable in the memory. Unsafe obtains the expected value of the data according to the memory offset address, and then performs the CAS operation. To ensure that the obtained value is the latest value, the variable is usually modified with volatile.

public final long getAndIncrement() {
    
    
    return unsafe.getAndAddLong(this, valueOffset, 1L);
}
// Unsafe类
public final long getAndAddLong(Object var1, long var2, long var4) {
    
    
    long var6;
    do {
    
    
        // 通过对象地址和偏移量获取变量的最新值
        var6 = this.getLongVolatile(var1, var2);
        // 满足条件进行CAS操作
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

    return var6;
}

CAS method underlying c++ source code implementation

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong e, jlong x))
  UnsafeWrapper("Unsafe_CompareAndSwapLong");
  Handle p (THREAD, JNIHandles::resolve(obj));
  // 内存地址
  jlong* addr = (jlong*)(index_oop_from_field_offset_long(p(), offset));
  if (VM_Version::supports_cx8())
    // Atomic::cmpxchg原子性比较和替换
    return (jlong)(Atomic::cmpxchg(x, addr, e)) == e;
  else {
    jboolean success = false;
    ObjectLocker ol(p, THREAD);
    if (*addr == e) { *addr = x; success = true; }
    return success;
  }
UNSAFE_END

3 Disadvantages of CAS

3.1 ABA problem

CAS is the comparison value, if the value is equal, it will be changed. There may be such a situation here, thread 1 gets the value of the variable 5, thread 2 changes the value to 10, and thread 3 changes the value back to 5. For thread 1, the value of the variable has not changed, but it is incorrect for subsequent operations such as counting.

Analysis: The ABA problem is caused by the circular transformation of the state value of the variable. If the value of the variable can only be transformed in one direction, it will not form a circle, and there will be no ABA problem.

Solution: You can refer to the handling of database optimistic locking, add the version number, the version number will change when the variable is updated. Replace the comparison variable value by comparing the version number. Similar to the Fast-Fail mechanism of the collection, check whether the modCount value is consistent.

3.2 Long spin time brings performance cost

Take AtomicLong as an example. In a high-concurrency scenario, if the thread has been unable to perform CAS operations, the internal dowhile loop will continue to spin and consume CPU.

Spin understanding

Spin is a retry strategy, which can be either an optimistic lock retry strategy or a pessimistic lock retry strategy. For example, many methods of AticLong call the getAndAddLong method, and the spin + CAS operation is used internally. As for the pessimistic lock, ReentrantLock's timeout method of acquiring the lock, tryLock, facilitates the use of a for loop.

4 Unsafe

Unsafe is the core class of CAS. Java cannot directly access the underlying operating system, but through local methods. The Unsafe class in JDK calls local methods at the bottom layer and provides hardware-level atomic operations.

The Unsafe class belongs to the rt.jar package and is loaded using the bootstrap class loader, while the ordinary main function class is loaded using the AppClassLoader. Unsafe directly manipulates the memory, so it is called an unsafe class and cannot be called arbitrarily.

The Unsafe instance can be obtained through reflection.

Field field = Unsafe.class.getDeclareField("theUnsafe");
field.setAccessible(true);

The case code location of this article: https://gitee.com/dtyytop/advanced-java

Guess you like

Origin blog.csdn.net/LIZHONGPING00/article/details/114006951