Java Basics: CAS Detailed Explanation


Question: 1. Do you know CAS? How is it achieved?
2. If there is a shared integer variable n, such as the number of votes, it needs to be reduced by 1 every time it is sold. Is it okay to use n-? Will there be problems?

Answer:
The full name of CAS is Compare and swap, which is a CPU concurrency primitive .

Its function is to judge whether the value of a certain location in the memory is the expected value, and if so, change it to the latest value. This process is atomic .

The CAS concurrency primitives embodied in the JAVA language are the various methods in the sun.misc.Unsafe class. Call the CAS method in the Unsafe class, and the JVM will help us implement the CAS assembly instruction . This is a completely hardware-dependent feature by which atomic operations are achieved.

One of the functions of CAS, for example, can deal with the problem that volatile does not guarantee atomicity in the previous blog post, and can solve the problem of n++ of a single shared variable in a multi-threaded environment.

If you want to understand the role of CAS, you must first fully understand the role of volatile. If you are not sure, you can read this blog post first ( Java Basics: Detailed Explanation of volatile ), and then understand CAS, otherwise you may not understand it, because some concepts , will not be repeated in this paper.

1. Detailed explanation of CAS

As mentioned before, volatile does not guarantee atomicity, so there are problems when multi-threading executes n++ operations. One of the solutions is to use the AtomicInteger class for processing. Next, we will analyze why using the AtomicInteger class can solve this problem.

1.1 Understand the compareAndSet method of the AtomicInteger class

First look at the compareAndSet method of the AtomicInteger class. Still take buying tickets as an example, the initial number of tickets is 10, and two threads buy tickets at the same time, assuming that thread 1 buys tickets first, and thread 2 buys tickets later.

The running result is shown in the figure below. It can be seen that thread 2 returns false, that is, the ticket was not successfully purchased.

package com.koping.test;

import java.util.concurrent.atomic.AtomicInteger;

public class CasDemo {
    
    
    public static void main(String[] args) {
    
    
        AtomicInteger atomicInteger = new AtomicInteger(10);

        // 业务逻辑:n行代码

        // 线程1,抢到票就将票数减1
        System.out.println(atomicInteger.compareAndSet(10, 9) + "\t 现在票数为: " + atomicInteger.get());

        // 线程2,抢到票就将票数减1
        System.out.println(atomicInteger.compareAndSet(10, 9) + "\t 现在票数为: " + atomicInteger.get());
    }
}

insert image description here

Through the above example, combined with the calling relationship of the compareAndSet method below, it can be well understood. This method is to judge whether the current value of the object is equal to the expectedValue?
If they are equal, set the value of the current object to newValue and return true;
if they are not equal, return false;
insert image description here
insert image description here

1.2 Understand the getAndIncrement method of the AtomicInteger class

Next, let's analyze why using the getAndIncrement method of the AtomicInteger class can replace n++ in multi-threaded situations, and there will be no atomicity problems.

We can look at the source code of the getAndIncrement method.
1. On line 183, the getAndAddInt method of the Unsafe class is called: U.getAndAddInt(this, VALUE, 1);
  1.1 In the getAndAddInt method of the unsafe class, first obtain the latest
    value on the current main memory according to the current object and offset , and then call the compareAndSetInt method to determine whether the value has been modified successfully?
    If the addition of 1 is successful, it will jump out of the loop;
    if it is unsuccessful, it will enter the loop again, reacquire the latest value of the main memory, and then judge again.
insert image description here
insert image description here
insert image description here

At this point, it is very important to fully understand what is the Unsafe class first!

1. The Unsafe class is the core class of CAS. Since the Java method cannot directly access the underlying system, it needs to be accessed through a local (native) method. But the Unsafe class can directly manipulate data in specific memory .

The Unsafe class exists in the sun.misc package, and all methods in this class are natively modified, that is to say, the methods in the Unsafe class directly call the underlying resources of the operating system to perform corresponding tasks .

2. The offset in the getAndAddInt method indicates the offset address of the variable value in the memory, and the Unsafe class obtains data according to the memory offset address.
3. The value of the AtomicInteger class is modified with volatile, thus ensuring the visibility between multiple threads.
insert image description here
4. The full name of CAS is Compare and swap, which is a CPU concurrency primitive .
Its function is to judge whether the value of a certain location in the memory is the expected value, and if so, change it to the latest value. This process is atomic .

The CAS concurrency primitives embodied in the JAVA language are the various methods in the sun.misc.Unsafe class. Call the CAS method in the Unsafe class, and the JVM will help us implement the CAS assembly instruction. This is a completely hardware-dependent feature by which atomic operations are achieved.

Since CAS is a system primitive, the primitive belongs to the category of the operating system and is composed of several instructions. It is a process for the user to complete a certain function, and the execution of the primitive must be continuous. Allowed to be interrupted. Therefore, CAS is a CPU atomic instruction and will not cause the so-called data inconsistency problem .

1.3 Analysis of using the AtomicInteger class instead of n++

Combined with the analysis of the getAndIncrement method of the AtomicInteger class in 1.2, it should be easy to understand why the getAndIncrement method of the AtomicInteger class can be used instead of n++ in the case of multi-threading.

Because the getAndIncrement method will call the CAS method of the Unsafe class, and this method is a CPU atomic instruction that will not be interrupted, so in the case of multi-threading, the result after n++ can be guaranteed to be correct. Therefore, the problem that volatile does not guarantee atomicity can be solved.

At the same time, look at the getAndAddInt method, you can see that it first obtains the latest value of the object in the main memory in the loop, and then uses CAS to ensure that the replaced value is the same as before, that is to say, it is replaced and written back to the main memory before the current thread. Memory.

If it fails, it means that other threads have modified the value of the object, so it needs to be re-acquired and then written back to main memory.

insert image description here
zuo

2. Disadvantages of CAS

There are three main disadvantages of CAS:
1) Long cycle time and high overhead. Maybe thread 1 finds that the value has been modified every time it wants to perform a CAS operation, and then it has been in the loop.
2) Only one atomic operation of a shared variable can be guaranteed. If there are multiple shared variable operations, synchronized will eventually be used.
3) ABA problem.
For example, the initial value of the object is 5
1) Thread 1 is going to change the value to 6
2) At this time, thread 2 seized the CPU and changed the value to 8
3) Then thread 3 seized the CPU and changed the value to 6
At this time Thread 1 finally seized the CPU, and then used CAS to find that the original value and the current value are both 6, and then the operation can be performed. But in fact, in this process, other threads have performed operations, and there may be other logical exceptions. This is the ABA problem.

Guess you like

Origin blog.csdn.net/xueping_wu/article/details/124571158