Java atomic variables

Overview

Multiple threads operating on shared variables (data on Java heap memory) will bring bugs. Java provides a lock mechanism (Lock) to manage multi-thread concurrency, such as synchronized, but it will bring additional performance overhead (thread blocking, context switching) Wait). In order to improve performance, Java introduced atomic variables to achieve multi-thread safety through lock-free algorithms, such as CAS.
Atomic variables are only a means to achieve multi-thread safety, and are suitable for the scenario of "read-modify-write" operations on a single shared variable, so its applicable scenarios are not widely synchronized.

Multithreading problem

First, implement a counter, the code is as follows:

public class Counter {
    private volatile int num;

    public void increment() {
        num++;
    }

    public static void main(String[] args) {
        Counter counter = new Counter();
        // 多线程递增计数器
        IntStream.range(0, 100).parallel().forEach(i -> counter.increment());
        // 打印结果
        System.out.println("counter: " + counter.num);
    }
}

Running the above code multiple times, the printed value is not 100, but 98, 97, etc.
This is a typical multi-threaded problem. Num ++ looks like a simple line of code, like an atomic operation, but it is not the case. The increment operation may be performed in three steps:

  1. Read the value of the current num variable
  2. Execute num + 1
  3. Assign the value after +1 to the num variable

Therefore, the updated value of multiple threads will be overwritten. For example, two threads get the value of num at the same time, 50 after performing the addition operation in their respective threads, and then update the value in the main memory to 51. But our expected value is 52.

Solved by synchronized

Add synchronized keyword to increment () method as follows:

// ...
public synchronized void increment() {
    num++;
}
// ...

Synchronized is the most commonly used lock in Java, to ensure that the "monitoring" code block can only be executed by one thread at the same time, so the final result is 100, correct.
However, this method will cause the thread that has not acquired the lock to hang, and a context switch will occur. This is the performance overhead caused by heavyweight locks.

Solved by atomic variable AtomicInteger

AtomicInteger class under the atomic package can solve the above problems, the code is as follows:

public class Counter {
    private AtomicInteger num = new AtomicInteger(0);

    public void increment() {
        while (true) {
            int oldValue = num.get();
            int newValue = oldValue + 1;
            if (num.compareAndSet(oldValue, newValue)) {
                return;
            }
        }
    }

    public static void main(String[] args) {
        Counter counter = new Counter();
        // 多线程递增计数器
        IntStream.range(0, 100).parallel().forEach(i -> counter.increment());
        // 打印结果
        System.out.println("counter: " + counter.num);
    }
}

Run the code and output 100.

CAS atomic operation

The atomic variables under the Java concurrent package use the CAS mechanism to implement atomic operations. The atomic operation mentioned here refers to the atomic operation (Atomic memory operation) of the CPU on a certain memory, which has the following characteristics:

  • Serialize the update operation of multiple threads to the same memory (to ensure the safety of multi-thread update data).
  • The three operations of read-modify-write cannot be interrupted, or the update operation will succeed or fail, and there will be no intermediate state (to ensure data integrity).
  • Only when the value in memory is the same as the expected value, the update operation will be performed (guarantee the correct logic).

In concurrent programming, CAS belongs to "optimistic locking", assuming that the probability of multi-threaded competition is very small, or the competition state will end in a short time. If the multi-threaded competition is very frequent, the CPU will be idle for a long time (busy waiting) Cause waste of resources. So, there is no silver bullet! Select the technical solution according to the scene.

CAS (Compare And Swap) requires specific CPU instruction support, so not all hardware platforms support CAS. The cross-platform nature of Java requires consistent API behavior, so on hardware platforms that do not support CAS, atomic will degenerate into a heavyweight lock.

to sum up

There are many ways to achieve multithreading, and choosing a reasonable technical solution according to the scenario can improve the performance of the program. This article briefly describes how atomic variables in Java solve multi-threaded problems and some concepts of CAS.

参考:
[1] Why do we use atomic variables instead of a volatile in Java?
[2] An Introduction to Atomic Variables in Java
[3] When to use AtomicReference in Java?
[4] Threads and Locks
[5] Compare-and-swap
[6] Understanding and Using Atomic Memory Operations

Guess you like

Origin www.cnblogs.com/junejs/p/12686921.html