Welcome to pay attention to github.com/hsfxuebao, I hope to help you, if you think it can trouble to click on the Star ha

Prior to CAS, multithreaded environments did not use atomic classes for thread-safety (basic data types), as shown in the code below:

public class T3 {
    volatile int number = 0;
    / / read
    public int getNumber(a) {
        return number;
    }
    // Write locking ensures atomicity
    public synchronized void setNumber(a) { number++; }}Copy the code

A multithreaded environment uses atomic classes to ensure thread-safety (basic data types), as shown below:

public class T3 {
    volatile int number = 0;
    / / read
    public int getNumber(a) {
        return number;
    }
    // Write locking ensures atomicity
    public synchronized void setNumber(a) {
        number++;
    }
    / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    AtomicInteger atomicInteger = new AtomicInteger();

    public int getAtomicInteger(a) {
        return atomicInteger.get();
    }

    public void setAtomicInteger(a) { atomicInteger.getAndIncrement(); }}Copy the code

1. CAS basic introduction

CAS (compare and swap) abbreviation, Chinese translation into compare and swap, the realization of concurrent algorithms often used a technology. It contains three operands — memory location, expected value, and updated value. When performing the CAS operation, compare the value of the memory location to the expected value:

  • If they match, the processor will automatically update the location value to the new value,
  • If they don't match, the processor does nothing, and multiple threads execute the CAS operation simultaneouslyOnly one will succeed.

1.1 the principle

CAS has three operands, the location memory value V, the old expected value A, and the updated value B to be modified. Change the memory value V to B if and only if the old expected value A and memory value V are the same, otherwise do nothing or start again

1.2 Hardware Level Guarantee

CAS is a non-blocking atomic operation provided by the JDK that hardware guarantees comparison-update atomicity. It’s non-blocking and it’s atomic, which means it’s more efficient and it’s more reliable because it’s guaranteed by hardware.

CAS is an atomic CPU instruction (CMPXCHG) that doesn’t cause data inconsistencies. Its underlying implementation of CAS methods such as compareAndSwapXXX is the CPU instruction CMPXCHG.

When executing the CMPXCHG command, the system will determine whether the current system is a multi-core system. If so, the bus will be locked. Only one thread will successfully lock the bus, and then the CAS operation will be executed. It’s just that the exclusion time is much shorter than synchronized, so performance is better in multithreaded situations.

The CAS Demo code is as follows:

public class CASDemo {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger atomicInteger = new AtomicInteger(5);

        System.out.println(atomicInteger.compareAndSet(5.2020) +"\t"+atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5.1024) +"\t"+atomicInteger.get()); }}Copy the code

1.3 Source Code Analysis

CompareAndSet () method

public final boolean compareAndSet(V expect, V update) {
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

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);
Copy the code

The above three methods are similar, and the four parameters are mainly explained.

  • Var1: indicates the object to be operated on
  • Var2: indicates the offset of the address of the property in the object to be manipulated
  • Var4: indicates the expected value for which data needs to be modified
  • Var5 / VAR6: indicates the new value to be changed to

This raises the question: What is the UnSafe class?

2. Understand the UnSafe

2.1 What do you understand about UnSafe

  • Unsafe is the core CAS class. Since Java methods cannot access the underlying system directly, they need to access it through native methods. Unsafe is a backdoor for manipulating data in specific memory. The Unsafe class exists in the Sun.misc package, and its internal method operations can manipulate memory directly like Pointers to C, because the execution of CAS operations in Java depends on the Unsafe class’s methods.

    • Note that all methods in the Unsafe class are native, meaning that methods in the Unsafe class call directly on the underlying operating system resources to perform their tasks
  • The variable valueOffset, which represents the offset address of the variable value in memory, is also used to obtain data.

  • The variable value is volatile, which ensures memory visibility across multiple threads.

2.2 i++ thread unsafe, the atomicInteger getAndIncrement ()?

CAS stands for compace-and-swap, And is a CPU concurrency primitive.

Its function is to determine whether a value at a location in memory is the expected value and, if so, change it to the new value. This process is atomic.

AtomicInteger mainly uses CAS (compare and swap) + volatile and native methods to ensure atomic operations, thus avoiding the high overhead of synchronized and greatly improving the execution efficiency.

The CAS concurrency primitive is the individual methods in the Sun.misc. Unsafe class in the JAVA language. Calling the CAS method in the UnSafe class, the JVM implements the CAS assembly directive for us. This is a completely hardware-dependent capability through which atomic operations are implemented.

Again, because the CAS is a kind of system primitives, primitive belong to the category of the operating system language, is made up of several instructions, to perform a certain function of a process, and carry out must be continuous, primitive is not permitted to interrupt in the implementation process, that is to say the CAS is a CPU atomic instruction, will not result in a so-called data inconsistency problem.

2.2.1 Source code analysis

new AtomicInteger().getAndIncrement()

Unsafe.java:

Suppose thread A and thread B execute the getAndAddInt operation at the same time (running on different cpus) :

  • The original value in AtomicInteger is 3, that is, the AtomicInteger value in the main memory is 3. According to the JMM model, thread A and thread B each hold A copy of the value with the value of 3 into their working memory.

  • Thread A gets value 3 via getIntVolatile(var1, var2), and thread A is suspended.

  • Thread B uses getIntVolatile(var1, var2) to obtain value 3. Thread B uses compareAndSwapInt (var1, var2) to obtain value 3. Thread B uses compareAndSwapInt to obtain value 4.

  • When thread A restores, it executes compareAndSwapInt and finds that the value 3 in its hand is inconsistent with the value 4 in the main memory, indicating that the value has been modified by other threads first. Then thread A fails to modify this time and can only read again.

  • Thread A retrieves value, which is always visible to other threads because value is volatile, and thread A continues to compareAndSwapInt until it succeeds.

2.3 Underlying assembly analysis

The Unsafe class, compareAndSwapInt, is a local method whose implementation is in broadening. CPP:

callAtomicThe function incmpxchgWhere parameter x is the value to be updated and parameter e is the value of the original memory

return (jint)(Atomic::cmpxchg(x, addr, e)) == e; 
Copy the code

Different CMPXCHG overloaded functions are called under different operating systems

This should give you an idea of what CAS really does, which is ultimately done by the operating system’s assembly instructions.

To sum up, just remember that CAS is implemented by hardware to improve efficiency at the hardware level, and the bottom line is left to the hardware to ensure atomicity and visibility

The implementation method is based on the hardware platform assembly instruction, in the Intel CPU (X86 machine), the use of assembly instruction CMPXCHG instruction.

If V is equal to E (compare), V will be set to the new value N (swap).

3. Atomic references

AtomicInteger is an AtomicInteger, AtomicReferenceDemo code shows as follows:

@Getter
@ToString
@AllArgsConstructor
class User{
    String userName;
    int    age;
}

public class AtomicReferenceDemo
{
    public static void main(String[] args)
    {
        User z3 = new User("z3".24);
        User li4 = new User("li4".26);

        AtomicReference<User> atomicReferenceUser = new AtomicReference<>();

        atomicReferenceUser.set(z3);
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString()); }}Copy the code

5. The spin lock

A spinlock is a situation where the thread attempting to acquire the lock does not block immediately, but instead attempts to acquire the lock in a circular fashion.

When the thread finds that the lock is occupied, it will loop through the status of the lock until it acquires it. This has the advantage of reducing the cost of thread context switching, but the disadvantage is that loops consume CPU

Implementation of a spin lock SpinLockDemo, code demo as follows:

/** * Title: Implement a spin lock * Spin lock benefits: loop comparison fetch without wait blocking. Thread A calls myLock and holds the lock for 5 seconds. Thread B then comes in and finds that * another thread is holding the lock, but not null, so it can only spin and wait until THREAD A releases the lock and thread B grabs it. * /
public class SpinLockDemo
{
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock(a)
    {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t come in");
        while(! atomicReference.compareAndSet(null,thread))
        {

        }
    }

    public void myUnLock(a)
    {
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"\t myUnLock over");
    }

    public static void main(String[] args)
    {
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
            spinLockDemo.myLock();
            try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }
            spinLockDemo.myUnLock();
        },"A").start();

        // Pause the thread for A while to make sure that thread A starts and completes before thread B
        try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            spinLockDemo.myLock();
            spinLockDemo.myUnLock();
        },"B").start(); }}Copy the code

6. CAS shortcomings

6.1 Long cycle time and high cost

We can see that the getAndAddInt method executes with a do while

If the CAS fails, attempts continue. If the CAS remains unsuccessful for a long time, it may incur significant CPU overhead.

6.2 ABA problem

CAS can cause “ABA problems”. An important prerequisite for CAS algorithm implementation is to take out the data in memory at a certain time and compare and replace it at the current time, so the time difference class will lead to data changes at this time.

Let’s say one thread fetches A from location V, and another thread two fetches A from location V, and thread two does something to change the value to B,

Then thread two changes the data at position V to A, and thread one performs the CAS operation and finds that there is still A in memory, and thread One succeeds. Although the CAS operation on thread one was successful, it does not mean that the process was problem-free.

ABADemo problem code is shown as follows:

public static void abaProblem(a)
    {
        new Thread(() -> {
            atomicInteger.compareAndSet(100.101);
            atomicInteger.compareAndSet(101.100);
        },"t1").start();

        // Pause for milliseconds
        try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            boolean b = atomicInteger.compareAndSet(100.20210308);
            System.out.println(Thread.currentThread().getName()+"\t"+"Modified successfully No:"+b+"\t"+atomicInteger.get());
        },"t2").start();
    }
Copy the code

Solution: Use the AtomicStampedReference class (atom reference with version number).

public class ABADemo
{
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100.1);

    public static void main(String[] args)
    {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t"+"-- Default version number:"+stamp);
            // let t4 have the same version number as T3, both of which are 1 for comparison
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

            atomicStampedReference.compareAndSet(100.101,stamp,stamp+1);
            System.out.println(Thread.currentThread().getName()+"\t"+"-- first version no. :"+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101.100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+"-- 2nd version no. :"+atomicStampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t"+"-- Default version number:"+stamp);
            // The upper T3 completes the ABA problem
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean result = atomicStampedReference.compareAndSet(100.20210308, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t"+"-- Operation successful No:"+result+"\t"+atomicStampedReference.getStamp()+"\t"+atomicStampedReference.getReference());
        },"t4").start();
    }

    public static void abaProblem(a)
    {
        new Thread(() -> {
            atomicInteger.compareAndSet(100.101);
            atomicInteger.compareAndSet(101.100);
        },"t1").start();

        // Pause for milliseconds
        try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            boolean b = atomicInteger.compareAndSet(100.20210308);
            System.out.println(Thread.currentThread().getName()+"\t"+"Modified successfully No:"+b+"\t"+atomicInteger.get());
        },"t2").start(); }}Copy the code

The resources

Java concurrent programming knowledge system Java concurrent programming art Java multi-threaded programming core technology Java concurrent implementation principle JDK source analysis