The principle of CAS atomic operation class under the atomic package of Java

foreword

Java's high-concurrency multi-threading, there are two ways to keep the program in atomic operations, namely CAS and Synchronized.
The usage scenarios of these two are as follows:   
For the case of less resource competition (light thread conflict), use synchronized synchronization locks for thread blocking and wake-up switching, as well as switching between user mode and kernel mode to waste and consume cpu resources; while CAS Based on hardware implementation, there is no need to enter the kernel, no need to switch threads, and there is less chance of operation spin, so higher performance can be obtained.
For severe resource competition (serious thread conflicts), the probability of CAS spin will be relatively high, thus wasting more CPU resources, and the efficiency is lower than synchronized.

Java memory model (Java Memory Model)

1. Introduction: If you want to understand the underlying principles of CAS, you must first know the memory model. The memory model itself is an abstract concept that does not actually exist in memory. It describes a set of specifications or rules, which are defined by this set of specifications. The access method of each variable (instance field, static field and elements constituting the array object) in the program..

2. Working principle: There is a main memory (main memory) in jvm, and each thread has its own working memory (working memory). When a thread operates on a variable attribute (variable), it will first be in Create a copy attribute in your own working memory, and write it into the main memory after the operation is completed. If multiple threads operate on the same variable attribute at the same time, unpredictable results may occur, so thread safety is to avoid this situation occurrence, as shown in the figure below:
insert image description here

3. The role of the volatile keyword in java:
  1. volatile can ensure the visibility of variables.
  2. Ensure orderliness. (to prevent instruction rearrangement).
  [When a thread modifies the value of a shared variable, the new value will be synchronized to the main memory immediately. When other threads read this variable, they will also be forced to pull the latest variable value from the main memory. ] But volatile cannot guarantee atomicity. Therefore, for synchronization, it is ultimately necessary to return to the lock mechanism. In java, there are two ways to ensure thread safety: one is to use the built-in lock (synchronized), and the other is to use the atomic class (under the java.util.concurrent package). For the atomic class, the mechanism it uses is CAS mechanism.

CAS core idea

1. A CAS involves the following operations: Suppose the original data V in the memory, the old expected value A, and the new value B to be modified:
compare whether A and V are equal. (Compare) Write B to V if compares equal. (exchange) Returns whether the operation was successful. When multiple threads perform CAS operations on a resource at the same time, only one thread can operate successfully, but it will not block other threads, and other threads will only receive a signal of operation failure. It can be seen that CAS is actually an optimistic lock.

2. For another example, as shown in the figure below, the value of variable A is stored in the main memory. To use the value of A in the thread, the value of A must be read from the main memory to the variable A of the working memory of the thread, and then calculated to become the value of C. , and finally write the B value back to the main memory A variable. This is the case for multiple threads to share the A value. The core of CAS is to compare the main memory A value with the thread's working memory A value before writing the C value to the main memory A. If they are not the same, it proves that the A value has been changed by other threads at this time. The A value is assigned to the A variable of the thread working memory, and the thread recalculates to obtain the C value of the working memory. If they are the same, the C value of the working memory is assigned to the A variable of the main memory.
![![Insert picture description here](https://img-blog.csdnimg.cn/b73d456712a74851a04254f9fe9aba46.png](https://img-blog.csdnimg.cn/9574fc73c04b4afea89a4a80d7e5e6fd.png

AtomicReference类

The objectFieldOffset() method of the UnSafe class is a local method. This method is used to get the memory address of the "original value", and the return value is valueOffset. In addition, value is a volatile variable that is visible in memory, so JVM can guarantee that any thread can always get the latest value of the variable at any time. As shown below:
insert image description here

//比较和设置新对象操作
public final boolean compareAndSet(V expect, V update) {
		//开始调用native层代码,如果当前值==期望值,则原子地将值设置为给定的更新值。
        return U.compareAndSwapObject(this, VALUE, expect, update);
    }
//原子地设置为给定值并返回旧值。
public final V getAndSet(V newValue) {
        return (V)U.getAndSetObject(this, VALUE, newValue);
    }

Classes under the java.util.concurrent.atomic package

Atom classes in JUC can be divided into four categories
insert image description here

basic type

Updating primitive types atomically

AtomicInteger: integer atomic class

AtomicLong: long integer atomic class

AtomicBoolean: Boolean atomic class

array type

Update an element in an array atomically

AtomicIntegerArray: integer array atomic class

AtomicLongArray: long integer array atomic class

AtomicReferenceArray: reference type array atomic class

reference type

AtomicReference: reference type atomic class

AtomicStampedReference: Atomic update reference type with a version number. This class associates an integer value with a reference, which can be used to solve the atomic update data and the version number of the data, and can solve the ABA problem that may occur when using CAS for atomic update.

AtomicMarkableReference : A reference type with a marked bit that is atomically updated

Object property modification type

AtomicIntegerFieldUpdater: An updater for atomically updating integer fields

AtomicLongFieldUpdater: An updater for atomically updating long integer fields

AtomicReferenceFieldUpdater: An updater for atomically updating reference type fields

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

Problems and Solutions Caused by CAS Mechanism

1. ABA problem:
Thread 1 is blocked after fetching A. At this time, thread 2 changes A in the memory to B, and then changes it to A after a series of operations. At this time, thread 1 resumes execution, fetching A in the memory and the A makes a comparison and finds that there is no change, and continues to execute.

However, although the two A may be the same at this time, they have actually been modified. For example, what thread 1 needs to replace is a top of the stack, from A to B, but thread 2 seizes the time slice before execution, makes a series of pop operations on the stack, and then pushes A into the stack. At this time, thread 1 After recovery, it is found that the top of the stack is still A, so it is replaced with B, but at this time the stack is no longer the original stack.

Solution idea - version number:
add the comparison of the version number when comparing, and modify the version number every time it is modified.
AtomicStampedReference starting from 1.5 is the version number comparison used.

2. High execution overhead:
If CAS fails for a long time, it will keep spinning and looping, which will generate a lot of execution overhead. And in order to avoid memory order conflicts at the end of the spin, the CPU will rearrange the pipeline, which will seriously affect the CPU performance.

Solution - pause command:

The pause command can make the CPU sleep for a short period of time when the spin fails and then continue to spin, so that the frequency of read operations is much lower, and the cost of pipeline rearrangement caused by resolving memory order conflicts will be much smaller.

Memory order conflict - when the spin lock is about to be released, the lock-holding thread will have a store command, and the threads spinning outside will issue their own load commands, and there is no happen-before sorting here, so the processor is Out-of-order execution, so in order to avoid the pipeline clearing and reordering before the load appears in the store, it will seriously affect the CPU efficiency. The function of the Pause instruction is to reduce the number of parallel loads, thereby reducing the time spent on reordering. (If you don’t understand load and store, you can look at the information of JMM (java memory model))

Guess you like

Origin blog.csdn.net/weixin_44715716/article/details/127009399