JUC Lecture 10: Detailed explanation of CAS, Unsafe and atomic classes

JUC Lecture 10: Detailed explanation of CAS, Unsafe and atomic classes

Most classes in JUC are implemented through volatile and CAS. CAS essentially provides a lock-free solution, while Synchronized and Lock are mutually exclusive lock solutions; Java atomic classes essentially use CAS, and the bottom layer of CAS is through Implemented by the Unsafe class. This article is the tenth lecture of JUC and will explain CAS, Unsafe and atomic classes in detail.

1. Understand the interview questions from major BAT companies

Please continue with these questions, which will help you to better understand the relevant knowledge points to a great extent.

  • What are the implementation methods of thread safety? Mutex lock, lock-free, stack closure, ThreadLocal
  • What is CAS?
  • CAS usage examples, give examples combined with AtomicInteger?
  • What are the problems with CAS?
  • What solutions does Java provide to solve these problems?
  • The underlying implementation of AtomicInteger? CAS+volatile
  • Please explain your understanding of the Unsafe class?
  • Tell me about your understanding of Java atomic classes? It contains 13 categories and 4 groups of categories. Tell us about their functions and usage scenarios.
  • What is AtomicStampedReference?
  • How does AtomicStampedReference solve ABA? Pair is used internally to store element values ​​and their version numbers
  • What other classes in java can solve the problem of ABA? AtomicMarkableReference

2、CAS

As we mentioned earlier, thread-safe implementation methods include:

  • Mutually exclusive synchronization: synchronized and ReentrantLock
  • Non-blocking synchronization: CAS, AtomicXXXX
  • No synchronization solution: stack closure, Thread Local, reentrant code

For details, please refer to: JUC Lecture 2: Theoretical Basis of Java Concurrency: Java Memory Model (JMM) and Threads . Here we will focus on CAS.

2.1. What is CAS

The full name of CAS is Compare-And-Swap, which literally means comparison and exchange. It is an atomic instruction of the CPU. Its function is to let the CPU first compare whether two values ​​​​are equal, and then atomically update the value of a certain position. After investigation, it was found that its implementation is based on the assembly instruction of the hardware platform, which means that CAS is Implemented by hardware, the JVM only encapsulates assembly calls, and those AtomicInteger classes use these encapsulated interfaces. Simple explanation: CAS operation requires inputting two values, an old value (the expected value before the operation) and a new value. During the operation, first compare whether the old value has changed. If there is no change, then exchange it to the new value. , will not be exchanged if there is a change.

CAS operations are atomic, so when multiple threads use CAS to update data concurrently, no locks are needed. CAS is used extensively in the JDK to update data and prevent locking (synchronized heavyweight locks) to maintain atomic updates.

I believe everyone is familiar with sql, which is similar to the conditional update in sql: update set id=3 from table where id=2. Because a single SQL statement is executed atomically, if multiple threads execute this SQL statement at the same time, only one can be updated successfully.

2.2. CAS usage examples

If CAS is not used, under high concurrency, if multiple threads modify the value of a variable at the same time, we need synchronized locking (some people may say that Lock can be used to lock, and the underlying AQS of Lock also acquires locks based on CAS).

public class Test {
    
    
    private int i=0;
    public synchronized int add(){
    
    
        return i++;
    }
}

Java provides us with the AtomicInteger atomic class (the bottom layer updates data based on CAS), which can achieve data consistency in multi-threaded concurrency scenarios without locking.

public class Test {
    
    
    private  AtomicInteger i = new AtomicInteger(0);
    public int add(){
    
    
        return i.addAndGet(1);
    }
}

CAS will not be used directly in the project, but various tool classes implemented by CAS: for example: AtomicInteger, AtomicReference, LongAdder, etc.

  • Scenario 1: Use AtomicInteger to count the number of SPUs, count the number of excel parsed rows, and count message push batches;
  • Scenario 2: Use LongAdder to count the number of cache requests;
  • Scenario 3: The most classic application of AtomicInteger, counting the number of threads;
  • Scenario 4: Use AtomicReference to temporarily store exceptions and save object information. When assembling the SPU, use AtomicReference to save brand information and model information.

2.3. CAS issues

The CAS mode is optimistic locking, and synchronized is pessimistic locking. Therefore, using CAS to solve concurrency problems usually has better performance.

However, there are several problems with using CAS:

1. ABA problem

Because CAS needs to check whether the value has changed when operating the value. For example, if there is no change, it will be updated. However, if a value is originally A, becomes B, and then becomes A, then when using CAS to check, it will It is found that its value has not changed, but it actually has changed.

The solution to the ABA problem is to use version numbers. Append the version number in front of the variable, and add 1 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. What the compareAndSet method of this class does is first check if the current reference is equal to the expected reference, and check if the current flag is equal to the expected flag, and if all are equal, atomically set the value of the reference and the flag to the given updated value.

2. Long cycle time and high cost

If spin CAS fails for a long time, it will bring very large execution overhead to the CPU. If the JVM can support the pause instruction provided by the processor, the efficiency will be improved to a certain extent. The pause instruction has two functions: first, it can delay the pipeline execution command (de-pipeline) so that the CPU does not consume too much execution resources. The delay time depends on the specific implementation version. On some processors, the delay time is zero; secondly, it can avoid the CPU pipeline being cleared (CPU Pipeline Flush) due to memory order conflict (Memory Order Violation) when exiting the loop, thereby improving the execution efficiency of the CPU.

3. Only atomic operations on a shared variable can be guaranteed.

When performing an operation on a shared variable, we can use cyclic CAS to ensure atomic operations. However, when operating on multiple shared variables, cyclic CAS cannot guarantee the atomicity of the operation. In this case, locks can be used.

Another trick is to merge multiple shared variables into one shared variable for operation. For example, there are two shared variables i = 2, j = a, merge ij = 2a, and then use CAS to operate ij.

Starting from Java 1.5, JDK provides the AtomicReference class to ensure the atomicity between reference objects, so that multiple variables can be placed in one object to perform CAS operations .

3. Detailed explanation of UnSafe class

Above we learned that Java atomic classes are implemented through the UnSafe class. This section mainly analyzes the UnSafe class. The UnSafe class is widely used in CAS operations in JUC.

Unsafe is a class located under the sun.misc package. It mainly provides some methods for performing low-level, unsafe operations, such as direct access to system memory resources, independent management of memory resources, etc. These methods improve Java operating efficiency and enhance Java The language's underlying resource operation capabilities play a big role. However, since the Unsafe class gives the Java language the ability to operate memory space similar to C language pointers, this undoubtedly increases the risk of pointer-related problems in the program. Excessive and incorrect use of the Unsafe class in a program will increase the probability of program errors, making a safe language like Java no longer "safe". Therefore, you must use Unsafe with caution.

Although the methods in this class are public, there is no way to use them, and the JDK API documentation does not provide any explanation about the methods of this class. All in all, the use of Unsafe classes is restricted. Only authorized code can obtain instances of this class. Of course, classes in the JDK library can be used at will.

Let’s first look at this picture to see the overall function of the UnSafe class:

img

As shown in the figure above, the APIs provided by Unsafe can be roughly divided into memory operations, CAS, Class-related, object operations, thread scheduling, system information acquisition, memory barriers, array operations, etc. The following will describe their related methods and application scenarios. Detailed introduction.

3.1. Unsafe and CAS

Decompiled code:

public final int getAndAddInt(Object paramObject, long paramLong, int paramInt){
    
    
    int i;
    do
      	i = getIntVolatile(paramObject, paramLong);
    while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
    return i;
}

public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2){
    
    
    long l;
    do
      	l = getLongVolatile(paramObject, paramLong1);
    while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));
    return l;
}

public final int getAndSetInt(Object paramObject, long paramLong, int paramInt){
    
    
    int i;
    do
      	i = getIntVolatile(paramObject, paramLong);
    while (!compareAndSwapInt(paramObject, paramLong, i, paramInt));
    return i;
}

public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2){
    
    
    long l;
    do
      	l = getLongVolatile(paramObject, paramLong1);
    while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2));
    return l;
}

public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2){
    
    
    Object localObject;
    do
      	localObject = getObjectVolatile(paramObject1, paramLong);
    while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));
    return localObject;
}

It was found from the source code that the spin method is used internally for CAS update (while loop performs CAS update, if the update fails, the loop will retry).

It was also discovered from the Unsafe class that atomic operations actually only support the following three methods.

public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);

public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);

public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);

We found that Unsafe only provides 3 CAS methods: compareAndSwapObject, compareAndSwapInt and compareAndSwapLong. They are all native methods.

3.2. Unsafe bottom layer

Let's take a look at Unsafe's compareAndSwap* method to implement CAS operations. It is a local method and the implementation is located in 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

You can see that it Atomic::cmpxchgimplements comparison and replacement operations. The parameter x is the value to be updated, and the parameter e is the value of the original memory.

If it is Linux x86, Atomic::cmpxchgthe method is implemented as follows:

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

The implementation of x86 in Windows is as follows:

inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
    
    
    int mp = os::isMP(); //判断是否是多处理器
    _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:

If it is a multi-processor, add the lock prefix to the cmpxchg instruction. Otherwise, omit the lock prefix (a single processor will not need the memory barrier effect provided by the lock prefix). The lock prefix here uses the processor's bus lock ( the latest processors use cache locks instead of bus locks to improve performance ).

cmpxchg(void* ptr, int old, int new), if the values ​​of ptr and old are the same, write new to ptr memory, otherwise return the value of ptr. The entire operation is atomic. Under the Intel platform, lock cmpxchg is used to implement it, and lock is used to trigger the cache lock, so that if another thread wants to access the memory of ptr, it will be blocked.

3.3. Other functions of Unsafe

Unsafe provides hardware-level operations, such as obtaining the location of a property in memory, such as modifying the field value of an object, even if it is private. However, Java itself is designed to shield the underlying differences, and there is rarely such a need for general development.

Give two examples, for example:

public native long staticFieldOffset(Field paramField);

This method can be used to obtain the memory address offset of a given paramField. This value is unique and fixed for the given field.

Another example:

public native int arrayBaseOffset(Class paramClass);
public native int arrayIndexScale(Class paramClass);

The former method is used to obtain the offset address of the first element of the array, and the latter method is used to obtain the conversion factor of the array, which is the incremental address of the elements in the array.

Finally, let’s look at three methods:

public native long allocateMemory(long paramLong);
public native long reallocateMemory(long paramLong1, long paramLong2);
public native void freeMemory(long paramLong);

They are used to allocate memory, expand memory and release memory respectively.

For more related functions, I recommend you read this article: From the Meituan technical team: Java magic class: Unsafe application analysis

4、AtomicInteger

4.1. Usage examples

Taking AtomicInteger as an example, commonly used APIs:

public final int get() //获取当前的值
public final int getAndSet(int newValue) //获取当前的值,并设置新的值
public final int getAndIncrement() //获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
void lazySet(int newValue) //最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

Compared with the advantages of Integer, variables can be incremented in multi-threading:

private volatile int count = 0;

// 若要线程安全执行执行 count++,需要加锁
public synchronized void increment() {
    
    
    count++;
}
public int getCount() {
    
    
    return count;
}

After using AtomicInteger:

private AtomicInteger count = new AtomicInteger();
public void increment() {
    
    
    count.incrementAndGet();
}

// 使用 AtomicInteger 后,不需要加锁,也可以实现线程安全
public int getCount() {
    
    
    return count.get();
}

4.2. Source code analysis

public class AtomicInteger extends Number implements java.io.Serializable {
    
    
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    static {
    
    
        try {
    
    
            //用于获取value字段相对当前对象的“起始地址”的偏移量
            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) {
    
     throw new Error(ex); }
    }

    private volatile int value;

    //返回当前值
    public final int get() {
    
    
        return value;
    }

    //递增加detla
    public final int getAndAdd(int delta) {
    
    
        //三个参数,1、当前的实例 2、value实例变量的偏移量 3、当前value要加上的数(value+delta)。
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

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

We can see that the bottom layer of AtomicInteger uses volatile variables and CAS to change data.

  • Volatile guarantees the visibility of threads. When multiple threads are concurrent, if one thread modifies data, it can ensure that other threads can see the modified value immediately;
  • CAS guarantees the atomicity of data updates.

5. Extended to all atomic classes: 12 in total

12 atomic operation classes are provided in JDK.

5.1. Basic types of atomic updates

Use atomic methods to update basic types. The Atomic package provides the following three classes.

  • AtomicBoolean: Atomic update Boolean type.
  • AtomicInteger: Atomic update integer type.
  • AtomicLong: Atomic update long integer.

The methods provided by the above three classes are almost identical. You can refer to the related methods in AtomicInteger above.

5.2. Atomic update array

To update an element in an array atomically, the Atomic package provides the following three classes:

  • AtomicIntegerArray: Atomicly updates elements in an integer array.
  • AtomicLongArray: Atomicly updates elements in a long integer array.
  • AtomicReferenceArray: Atomicly updates elements in a reference type array.

The most commonly used methods of these three classes are the following two methods:

  • get(int index): Get the element value with index index.
  • compareAndSet(int i, E expect, E update): Atomically sets the element at array position i to the update value if the current value is equal to the expected value.

Give an example of AtomicIntegerArray:

import java.util.concurrent.atomic.AtomicIntegerArray;

public class Demo5 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        AtomicIntegerArray array = new AtomicIntegerArray(new int[] {
    
     0, 0 });
        System.out.println(array);
        System.out.println(array.getAndAdd(1, 2));
        System.out.println(array);
    }
}

Output result:

[0, 0]
0
[0, 2]

5.3. Atomic update reference type

The Atomic package provides the following three classes:

  • AtomicReference: Atomic update reference type.
  • AtomicStampedReference: Atomic update reference type, internally uses Pair to store element values ​​and their version numbers.
  • AtomicMarkableReferce: Atomicly update a reference type with a mark bit.

The methods provided by these three classes are similar. First, construct a reference object, then set the reference object into the Atomic class, and then call some methods such as compareAndSet to perform atomic operations. The principles are all based on Unsafe implementation, but AtomicReferenceFieldUpdater is slightly different. Update The fields must be modified with volatile.

Give an AtomicReference example:

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceTest {
    
    
    
    public static void main(String[] args){
    
    

        // 创建两个Person对象,它们的id分别是101和102。
        Person p1 = new Person(101);
        Person p2 = new Person(102);
        // 新建AtomicReference对象,初始化它的值为p1对象
        AtomicReference ar = new AtomicReference(p1);
        // 通过CAS设置ar。如果ar的值为p1的话,则将其设置为p2。
        ar.compareAndSet(p1, p2);

        Person p3 = (Person)ar.get();
        System.out.println("p3 is "+p3);
        System.out.println("p3.equals(p1)="+p3.equals(p1));
    }
}

class Person {
    
    
    volatile long id;
    public Person(long id) {
    
    
        this.id = id;
    }
    public String toString() {
    
    
        return "id:"+id;
    }
}

Result output:

p3 is id:102
p3.equals(p1)=false

Description of results:

  • When creating a new AtomicReference object ar, initialize it to p1.
  • Next, set it through the CAS function. If the value of ar is p1, set it to p2.
  • Finally, get the object corresponding to ar and print the result. The result of p3.equals(p1) is false. This is because Person does not override the equals() method, but uses the equals() method inherited from Object.java; and equals() in Object.java actually calls "==" is used to compare two objects, that is, to compare whether the addresses of the two objects are equal.

5.4. Atomic update field class

The Atomic package provides four classes for atomic field updates:

  • AtomicIntegerFieldUpdater: Updater for atomically updating integer fields.
  • AtomicLongFieldUpdater: Updater for atomically updating long integer fields.
  • AtomicReferenceFieldUpdater: It has been mentioned above and will not be repeated here.

These four classes are used in similar ways, atomically updating field values ​​based on reflection. To update a field class atomically requires two steps:

  • The first step is that because the atomic update field classes are all abstract classes, you must use the static method newUpdater() to create an updater every time you use it, and you need to set the class and attributes you want to update.
  • In the second step, the fields of the updated class must be modified with public volatile.

for example:

public class TestAtomicIntegerFieldUpdater {
    
    

    public static void main(String[] args){
    
    
        TestAtomicIntegerFieldUpdater tIA = new TestAtomicIntegerFieldUpdater();
        tIA.doIt();
    }

    public AtomicIntegerFieldUpdater<DataDemo> updater(String name){
    
    
        return AtomicIntegerFieldUpdater.newUpdater(DataDemo.class, name);

    }

    public void doIt(){
    
    
        DataDemo data = new DataDemo();
        System.out.println("publicVar = "+ updater("publicVar").getAndAdd(data, 2));
        /*
         * 由于在DataDemo类中属性value2/value3,在TestAtomicIntegerFieldUpdater中不能访问
         * */
        //System.out.println("protectedVar = "+updater("protectedVar").getAndAdd(data,2));
        //System.out.println("privateVar = "+updater("privateVar").getAndAdd(data,2));

        //System.out.println("staticVar = "+updater("staticVar").getAndIncrement(data));//报java.lang.IllegalArgumentException
        /*
         * 下面报异常:must be integer
         * */
        //System.out.println("integerVar = "+updater("integerVar").getAndIncrement(data));
        //System.out.println("longVar = "+updater("longVar").getAndIncrement(data));
    }

}

class DataDemo{
    
    
    public volatile int publicVar=3;
    protected volatile int protectedVar=4;
    private volatile  int privateVar=5;

    public volatile static int staticVar = 10;
    //public  final int finalVar = 11;

    public volatile Integer integerVar = 19;
    public volatile Long longVar = 18L;

}

Let’s talk about some slight restrictions and constraints on the use of AtomicIntegerFieldUpdater. The constraints are as follows:

  • The field must be of volatile type and is guaranteed to be immediately visible when sharing variables between threads.eg: volatile int value = 3;
  • The description type of the field (modifier public/protected/default/private) is consistent with the relationship between the caller and the operation object field. That is to say, the caller can directly operate the object fields, so it can perform atomic operations through reflection. But for the fields of the parent class, the subclass cannot directly operate, although the subclass can access the fields of the parent class;
  • It can only be an instance variable, not a class variable, which means the static keyword cannot be added;
  • It can only be a modifiable variable, not a final variable, because the semantics of final are that it cannot be modified. In fact, the semantics of final conflict with volatile, and these two keywords cannot exist at the same time;
  • For AtomicIntegerFieldUpdater and AtomicLongFieldUpdater, only int/long type fields can be modified, and their packaging types (Integer/Long) cannot be modified. If you want to modify the packaging type, you need to use AtomicReferenceFieldUpdater.

6. Let’s talk about AtomicStampedReference solving the ABA problem of CAS

6.1. AtomicStampedReference solves ABA problem

AtomicStampedReference mainly maintains a pair object containing an object reference and an integer "stamp" that can be automatically updated to solve ABA problems.

public class AtomicStampedReference<V> {
    
    
    private static class Pair<T> {
    
    
        final T reference;  //维护对象引用
        final int stamp;  //用于标志版本
        private Pair(T reference, int stamp) {
    
    
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
    
    
            return new Pair<T>(reference, stamp);
        }
    }
    private volatile Pair<V> pair;
    ....
    
    /**
      * expectedReference :更新之前的原始值
      * newReference : 将要更新的新值
      * expectedStamp : 期待更新的标志版本
      * newStamp : 将要更新的标志版本
      */
    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) ||
            // 构造新的Pair对象并CAS更新
            casPair(current, Pair.of(newReference, newStamp)));
    }

    private boolean casPair(Pair<V> cmp, Pair<V> val) {
    
    
        // 调用Unsafe的compareAndSwapObject()方法CAS更新pair的引用为新引用
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }
  • If the element value and version number have not changed and are the same as the new ones, return true;
  • If the element value and version number have not changed and are not exactly the same as the new ones, construct a new Pair object and execute CAS to update the pair.

It can be seen that the implementation in Java is consistent with the ABA solution we talked about above.

  • First, use version number control;
  • Secondly, the reference of the node (Pair) is not reused, and a new Pair is created each time as the object of CAS comparison instead of reusing the old one;
  • Finally, the element value and version number are passed in externally instead of the reference of the node (Pair).

6.2. Usage examples

public class AtomicTester {
    
    

    private static AtomicStampedReference<Integer> atomicStampedRef =
            new AtomicStampedReference<>(1, 0);

    public static void main(String[] args){
    
    
        first().start();
        second().start();
    }

    private static Thread first() {
    
    
        return new Thread(() -> {
    
    
            System.out.println("操作线程" + Thread.currentThread() +",初始值 a = " + atomicStampedRef.getReference());
            int stamp = atomicStampedRef.getStamp(); //获取当前标识别
            try {
    
    
                Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            boolean isCASSuccess = atomicStampedRef.compareAndSet(1, 2, stamp, stamp +1);  //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败
            System.out.println("操作线程" + Thread.currentThread() +",CAS操作结果: " + isCASSuccess);
        },"主操作线程");
    }

    private static Thread second() {
    
    
        return new Thread(() -> {
    
    
            Thread.yield(); // 确保thread-first 优先执行
            atomicStampedRef.compareAndSet(1, 2, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
            System.out.println("操作线程" + Thread.currentThread() +",【increment】 ,值 = "+ atomicStampedRef.getReference());
            atomicStampedRef.compareAndSet(2,1,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
            System.out.println("操作线程" + Thread.currentThread() +",【decrement】 ,值 = "+ atomicStampedRef.getReference());
        },"干扰线程");
    }
}

Output result:

操作线程Thread[主操作线程,5,main],初始值 a = 1
操作线程Thread[干扰线程,5,main],【increment】 ,值 = 2
操作线程Thread[干扰线程,5,main],【decrement】 ,值 = 1
操作线程Thread[主操作线程,5,main],CAS操作结果: false

6.3. What other classes in Java can solve the problem of ABA?

AtomicMarkableReference, it does not maintain a version number, but maintains a boolean type mark. The mark value has been modified. Please find out more.

7. Reference articles

おすすめ

転載: blog.csdn.net/qq_28959087/article/details/133274067