Java-CAS detailed explanation

CAS basic usage

https://blog.csdn.net/Hsuxu/article/details/9467651
CAS operation contains three operands-memory location (V), expected original value (A) and new value (B)
if and only if expected value When A and memory value V are the same, modify memory value V to B, otherwise do nothing

public static void main(String[] args) {
    
    
    //主内存中 atomicInteger 的初始值为 5
    AtomicInteger atomicInteger = new AtomicInteger(5);

    // 如果初始值是5,那么将初始值修改为1024,然后得修改后的值
    System.out.println(atomicInteger.compareAndSet(5, 1024) + "主内存中的最终值为: " + atomicInteger.get());

    System.out.println(atomicInteger.compareAndSet(5, 2048) + "主内存中的最终值为: " + atomicInteger.get());
}

/**
 * this:当前对象
 * valueOffSet:当前对象的内存地址偏移量,就是this的内存地址
*/
public final boolean compareAndSet(int expect, int update) {
    
       
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

CAS in JVM is implemented by calling JNI code, and by using C to call the underlying instructions of the CPU.

CAS disadvantages

https://blog.csdn.net/hsuxu/article/details/9467651
Although CAS is very efficient in solving atomic operations, CAS still has three major problems. ABA problem, the cycle time is long and the overhead is large and can only guarantee the atomic operation of a shared variable

1> ABA problem

Because CAS needs to check whether the next value has changed when operating the value, if there is no change, update it, but if a value was originally A, becomes B, and becomes A again, then you will find when you use CAS to check Its value has not changed, but it has actually changed. The solution to the ABA problem is to use the version number. Add the version number in front of the variable, and add one to the version number every time the variable is updated, then A-B-A will become 1A-2B-3A.

Starting from Java 1.5, the atomic package of JDK provides a class AtomicStampedReference to solve the ABA problem. The function of the compareAndSet method of this class is to first check whether the current reference is equal to the expected reference and whether the current flag is equal to the expected flag. If all are equal, the reference and the value of the flag are atomically set to the given update value.

Reference documents for ABA issues: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html

2> Long cycle time and high overhead

If spin CAS is unsuccessful for a long time, it will bring very large execution overhead to the CPU

// CountDownLatch 核心方法
protected boolean tryReleaseShared(int releases) {
    
    
    // Decrement count; signal when transition to zero
    for (;;) {
    
    
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

If the JVM can support the pause instruction provided by the processor, the efficiency will be improved. The
pause instruction has two functions. First, it can delay the de-pipeline execution of instructions, so that the CPU will not consume too much execution resources and delay. The time depends on the specific implementation version, and the delay time is zero on some processors. Second, it can avoid the CPU pipeline flush caused by memory order violation when exiting the loop, thereby improving CPU execution efficiency.

3> Can only guarantee the atomic operation of a shared variable

When performing operations on a shared variable, we can use cyclic CAS to ensure atomic operations, but when operating on multiple shared variables, cyclic CAS cannot guarantee the atomicity of the operation. At this time, locks can be used, or there is a The tricky way is to combine multiple shared variables into one shared variable to operate. For example, if there are two shared variables i=2, j=a, merge ij=2a, and then use CAS to operate ij. Starting from Java 1.5, the JDK provides the AtomicReference class to ensure the atomicity between referenced objects. You can put multiple variables in one object to perform CAS operations

AtomicReference

https://blog.csdn.net/qq_37385585/article/details/112778251
Package Value and Stamp into Pair objects for overall comparison

private static void testStamped() {
    
    
    AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(5, 1);
    boolean result = true;
    
    result = stampedReference.compareAndSet(5,10,1,2);
    System.out.println(MessageFormat.format("Reference result:{0};value:{1};version:{2}",result, stampedReference.getReference(),stampedReference.getStamp()));
    
    result = stampedReference.compareAndSet(10,5,2,3);
    System.out.println(MessageFormat.format("Reference result:{0};value:{1};version:{2}",result,  stampedReference.getReference(),stampedReference.getStamp()));
    
    result = stampedReference.compareAndSet(5, 100, 1, 2);
    System.out.println(MessageFormat.format("Reference result:{0};value:{1};version:{2}",result,  stampedReference.getReference(),stampedReference.getStamp()));
}


/**
 * expectedReference the expected value of the reference
 * newReference the new value for the reference
 * expectedStamp the expected value of the stamp
 * newStamp the new value for the stamp
 */
public boolean compareAndSet(V   expectedReference,V   newReference,int expectedStamp,int newStamp) {
    
    
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
            newStamp == current.stamp) ||
            casPair(current, Pair.of(newReference, newStamp)));
}

private boolean casPair(Pair<V> cmp, Pair<V> val) {
    
    
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

Guess you like

Origin blog.csdn.net/lewee0215/article/details/113100915