Java-JUC (3): Atomicity Variables and CAS Algorithm

atomicity

For concurrent programs to execute correctly, atomicity , visibility, and ordering must be guaranteed . As long as one is not guaranteed, it is possible to cause the program to behave incorrectly.

Atomicity: An operation or operations are either all executed and the execution is not interrupted, or they are not executed.

Visibility: When multiple threads access the same variable at the same time, one thread modifies the value of the variable, and other threads can immediately see the modified value.

Ordered: The order in which the program is executed is executed in the order in which the code is executed.

For a single thread, the jvm will reorder the instructions when executing the code. In order to improve the efficiency, the processor can optimize the input code. It does not guarantee that the execution order of each statement in the program is the same as the order in the code, but it will guarantee Saving the final execution result is consistent with the result of sequential execution of the code.

See an example below:

package com.dx.juc.test;

public class MyThread implements Runnable {
    private int serialNumber = 0;

    public void run() {
        System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
    }

    public int getSerialNumber() {
        return serialNumber++;
    }
}

transfer:

package com.dx.juc.test;

public class Main {
    public static void main(String[] args) {
        MyThread thread=new MyThread();
        for(int i=0;i<10;i++){
            new Thread(thread).start();
        }
    }
}

Output results (sometimes the following exceptions are thrown, but not always):

Thread-1:0
Thread-3:2
Thread-2:1
Thread-0:0
Thread-4:3
Thread-5:4
Thread-7:5
Thread-6:6
Thread-9:7
Thread-8:8

The reason for the above operation error:

1) The initial value serialNumer=0, thread-0 copies the value to its own workspace (private to the thread, and the workspace can also be called a cache), and then performs three operations:

Operation 1: int temp=serialNumber;//Copy serialNumber from main memory to your own workspace

Operation 2: serialNumber=serialNumber+1;// Perform operations in your own workspace

Operation 3: Flush serialNumber to main memory

2) Suppose thread-0 copies serialNumber=0 from the main memory to its own workspace before operation 3 is processed, and then thread-0 triggers operation 3. At this time, thread-1 does not know the main memory. The serialNumber in has been modified, it still uses serialNum=0 in the workspace, and also performs the same walk operation as thread-0.

When thread-1 triggers operation three (at this time, thread-0 has changed serialNubmer in main memory to 1) and does not know whether serialNumber has been operated by other threads, it refreshes the result of modification in its own workspace to main memory , serialNum=1 in thread-1 at this time, the subsequent operation is that the value of thread-1 overwrites the value of thread-0, in fact, their values ​​are the same.

3) Even adding volatile modification to int serialNumber cannot avoid this problem. It can be seen from this that volatile is not atomic.

In java, reading and assigning operations to variables of basic data types are atomic operations, that is, these operations are not interruptible, either executed or not.

X=10;    // Atomicity (simple reading, assigning numbers to variables) 
Y = x;   // Mutual assignments between variables, not atomic operations 
X++;     // Calculation operations on variables, as mentioned at this time Three operations int temp=X; X=X+1; X=temp, after three operations of acquisition, modification and assignment. 
x = x+1;

Statement 2 actually includes two operations. It first reads the value of x, and then writes the value of y. The two operations are atomic separately, but not atomic together.

Statements 3, 4: x++ x=x+1 include 3 operations: read the value of x, x+1, write x, so they are not atomic either.

Only statement 1 is atomic.

Note: Atomicity can be achieved through synchronized and Lock. Because synchronized and Lock can ensure that only one thread accesses the code block at any one time.

Solving Atomicity Problems with Atomic

After jdk1.5, under the java.util.concurrent.atomic package, it provides

A large number of atomic variables, which use the CAS algorithm internally.

The code above is modified as follows:

package com.dx.juc.test;

import java.util.concurrent.atomic.AtomicInteger;

public class MyThread implements Runnable {
    private AtomicInteger serialNumber = new AtomicInteger();

    public void run() {
        System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
    }

    public int getSerialNumber() {
        return serialNumber.getAndIncrement();
    }
}

Modifying int serialNumber to AtomicInteger serialNumber is atomic and no longer produces abnormal results.

CAS algorithm

CAS (Compare-And-Swap) is a kind of hardware support for concurrency, designed for multiprocessor operations, a special instruction in the processor to manage concurrent access to shared data.

CAS is a lock-free, non-blocking algorithm implementation. It is hardware support for concurrent operations and ensures the atomicity of data variables.

Cas contains 3 operands:

  1. memory value V
  2. Estimated value A
  3. update value B

V = B if and only if V == A; otherwise, do nothing.

In simple terms, CAS has 3 operands, the memory value V to be read and written, the old expected value A, and the new value B to be modified. If and only if the expected value A and the memory value V are the same, modify the memory value V to B, otherwise return V (do nothing, then re-fetch the main memory V value, and re-operate.). This is an optimistic locking idea, which believes that no other thread will modify it until it is modified.

 

Guess you like

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