Implementation principle of CAS in java

java provides three CAS operations in Unsafe:

compareAndSwapInt(),compareAndSwapObject(),compareAndSwapLong()

//Parameter meaning: object, attribute memory offset, attribute expected value, attribute update value
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

Offset: the object in memory contains object header and object instance data, and the object header accounts for 8 bytes. For 64 bit operating system, the compressed pointer accounts for 4 bytes, so we generally say that the object header accounts for 12 bytes; For example, for a test object, the offset of x is the object header of the object, that is, 12 bytes, and the offset of y is 16

The cas operation modifies the x variable of the Test class.

public class CASTest {

    public static void main(String[] args) {
        Test test = new Test();
        Unsafe unsafe = UnsafeFactory.getUnsafe();
        long xOffset = UnsafeFactory.getFieldOffset(unsafe, Test.class, "x");
        System.out.println(xOffset); //12
        long yOffset = UnsafeFactory.getFieldOffset(unsafe, Test.class, "y");
        System.out.println(yOffset); //16
        unsafe.compareAndSwapInt(test, xOffset, 0, 1);
        System.out.println(test.x);
    }
    
    static class Test {
        int x;
        int y;
    }
}

CAS can guarantee atomicity, but cannot guarantee order and visibility. Therefore, generally, CAS can be used with volatile to ensure thread safety. The bottom layer finally executes a CAS instruction (atomic operation modifies variable value) and compares the expected value with the actual value in memory. If the comparison results are equal, the old value (expected value) is returned, indicating that the CAS operation is successful. If they are not equal, the actual value in memory is returned, indicating that the CAS operation fails.

CAS implements thread safe operations

public class CASTest {

    private static int sum = 0;
    private static CASLock casLock = new CASLock();

    public static void main(String[] args) throws InterruptedException {
        for (int i=0; i<10; i++) {
            new Thread(() -> {
                for (;;) {
                    if (casLock.getState() == 0 && casLock.cas()) {
                        try {
                            for (int j = 0; j < 10000; j++) {
                                sum++;
                            }
                        } finally {
                            casLock.setState(0);
                        }
                        break;
                    }
                }
            }).start();
        }
        Thread.sleep(2000);
        System.out.println(sum);
    }
}
public class CASLock {

    private volatile int state = 0;
    private static final Unsafe UNSAFE;
    private static final long OFFSET;
    static {
        UNSAFE = UnsafeFactory.getUnsafe();
        OFFSET = UnsafeFactory.getFieldOffset(UNSAFE, CASLock.class, "state");
    }
    
    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public boolean cas() {
        return UNSAFE.compareAndSwapInt(this, OFFSET, 0, 1);
    }
}

The atomic classes under the juc package in jdk are thread safe through cas.

ABA problems can be solved by version number

LongAdder and DoubleAdder principles

Under high concurrency, CAS operation will have a large number of thread spins, resulting in a waste of thread resources. In order to improve the execution efficiency, the V value is divided into multiple variables. Multiple threads perform CAS operation on their own variables at the same time. After all threads complete the execution, all variables are accumulated and counted. Its idea is similar to the number of statistical elements of ConcurrentHashMap in jdk8. LongAdder and DoubleAdder also implement this idea. LongAdder defines the base variable and cell array variable, initializes and accumulates the cell array through hash, and finally accumulates all the numbers of base and cell array to get the result.

Keywords: Java Back-end Multithreading

Added by deeessay on Fri, 31 Dec 2021 20:50:13 +0200