Understanding of CAS mechanism (1)

Let's first look at a piece of code: start two threads, and let the static variable count loop accumulate 100 times in each thread.

public class CountTest {
    public static int count = 0;

    public  static  void main(String[] args) {
         // Open two threads 
        for ( int i = 0; i < 2; i++ ){
             new Thread(
                     new Runnable(){
                         public  void run(){
                             try {
                                Thread.sleep(10);
                            }
                            catch(InterruptedException e){
                                e.printStackTrace ();
                            }
                            // In each thread, increment count by 100 times 
                            for ( int j = 0; j < 100; j++ ){
                                    count++;
                            }
                        }
                    }).start();
        }
        try{
            Thread.sleep(2000);
        }
        catch(InterruptedException e){
            e.printStackTrace ();
        }
        System.out.println("count=" + count);
    }

}

Results of several different runs:

     
Because this code is not thread-safe, the final self-increment result is likely to be less than 200.
Method 1: Add synchronized synchronization lock

public class CountTest {
    public static int count = 0;

    public  static  void main(String[] args) {
         // Open two threads 
        for ( int i = 0; i < 2; i++ ){
             new Thread(
                     new Runnable(){
                         public  void run(){
                             try {
                                Thread.sleep(10);
                            }
                            catch(InterruptedException e){
                                e.printStackTrace ();
                            }
                            // In each thread, count is incremented 100 times 
                            for ( int j = 0; j < 100; j++ ){
                                 synchronized(CountTest.class){
                                count++;
                                }
                            }
                        }
                    }).start();
        }
        try{
            Thread.sleep(2000);
        }
        catch(InterruptedException e){
            e.printStackTrace ();
        }
        System.out.println("count=" + count);
    }
}

(By using "class name.class" to return an object of type Class, that is, to obtain a "class object" of a class)
After adding a synchronization lock, the operation of counting self-increment becomes an atomic operation, so the final output It must be count=200, and the code is thread-safe.
Although synchronized guarantees thread safety, in some cases, it is not an optimal choice. The key is performance issues.
The locks obtained by synchronized are all object locks, rather than a piece of code or method as a lock. Therefore, when multiple threads access the same object, which thread executes the method of the synchronized keyword first, which thread holds the lock of the object to which the method belongs, and other threads can only wait.
The Synchronized keyword will make the thread that has not obtained the lock resource enter the BLOCKED state, and then return to the RUNNABLE state after competing for the lock resource. This process involves the conversion of the operating system user mode and kernel mode, and the cost is relatively high.
Although Java 1.6 has optimized Synchronized and increased the transition from biased locks to lightweight locks to heavyweight locks, the performance is still low after the final transformation to heavyweight locks.

Method 2: Use the atomic operation class

The so-called atomic operation class refers to a series of wrapper classes starting with Atomic under the java.util.concurrent.atomic package. For example AtomicBoolean, AtomicInteger, AtomicLong. They are used for atomic operations of Boolean, Integer, Long types, respectively.


Introduce the AtomicInteger class in the code:

import java.util.concurrent.atomic.AtomicInteger;

public class CountTest {
    public static AtomicInteger count = new AtomicInteger(0);

    public  static  void main(String[] args) {
         // Open two threads 
        for ( int i = 0; i < 2; i++ ){
             new Thread(
                     new Runnable(){
                         public  void run(){
                             try {
                                Thread.sleep(10);
                            }
                            catch(InterruptedException e){
                                e.printStackTrace ();
                            }
                            // In each thread, increment count by 100 times 
                            for ( int j = 0; j < 100; j++ ){
                            count.incrementAndGet();    
                            }
                        }
                    }).start();
        }
        try{
            Thread.sleep(2000);
        }
        catch(InterruptedException e){
            e.printStackTrace ();
        }
        System.out.println("count=" + count);
    }
}

After using AtomicInteger, the final output is also guaranteed to be 200. And in some cases the code will perform better than Synchronized.
The underlying implementation of the Atomic operation class: The underlying implementation of the Atomic operation class uses the CAS mechanism.

What is CAS?
CAS is the abbreviation of the English word Compare And Swap, which translates to compare and replace.
The CAS mechanism uses three basic operands: the memory address V, the old expected value A, and the new value B to be modified.
When updating a variable, only when the expected value A of the variable and the actual value in the memory address V are the same, the value corresponding to the memory address V will be changed to B.
For example:
1. In the memory address V, a variable with a value of 10 is stored.

2. At this point, thread 1 wants to increase the value of the variable by 1. For thread 1, the old expected value A=10, and the new value to be modified B=11.

3. Before thread 1 wants to submit the update, another thread 2 takes the lead and updates the variable value in memory address V to 11 first.

4. Thread 1 starts to submit the update, first compares the actual value of A and address V (Compare), and finds that A is not equal to the actual value of V, and the submission fails.

5. Thread 1 retrieves the current value of memory address V and recalculates the new value it wants to modify. At this time, for thread 1, A=11, B=12. This retry process is called spinning.

 

6. Luckily this time, no other thread has changed the value of address V. Thread 1 performs a Compare and finds that the actual values ​​of A and address V are equal.

7. Thread 1 performs SWAP and replaces the value of address V with B, which is 12.

From an ideological point of view, Synchronized is a pessimistic lock, and it is pessimistic that the concurrency in the program is serious, so it is strictly guarded against. CAS is an optimistic lock, optimistic that the concurrency in the program is not so serious, so let the thread keep trying to update.

There is no absolute good or bad between CAS and Synchronized, the key depends on the usage scenario. In the case of very high concurrency, it is more appropriate to use synchronization locks.
CAS application:
1. Atomic series classes, and the underlying implementation of Lock series classes
2. In Java1.6 and above, before Synchronized is transformed into a heavyweight lock, the CAS mechanism will also be used.

Disadvantages of CAS:
1. Large CPU overhead
In the case of high concurrency, if many threads repeatedly try to update a certain variable, but the update fails all the time, the cycle will go back and forth, which will bring great pressure to the CPU.
2. The atomicity of the code block cannot be guaranteed. The
CAS mechanism guarantees only the atomic operation of a variable, but cannot guarantee the atomicity of the entire code block. For example, if you need to ensure that three variables are updated atomically together, you have to use Synchronized.
3. ABA problem
This is the biggest problem of the CAS mechanism. Here in the next description.

Reference: Comic: What is the CAS mechanism?

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325090539&siteId=291194637