Welcome to follow our wechat official account: Shishan100
My new course ** “C2C e-commerce System Micro-service Architecture 120-day Practical Training Camp” is online in the public account ruxihu Technology Nest **, interested students, you can click the link below for details:
120-Day Training Camp of C2C E-commerce System Micro-Service Architecture
Previously on
In the previous article, we talked about how volatile works. For details, see: What is Volatile? .
This article discusses atomic operations related to CAS in Java and packages, and how Java 8 can improve and optimize the performance of CAS operations.
The Atomic family of Atomic classes is often used in concurrent programming, JDK source code, and various open source projects. And in Java concurrent interview, this part also belongs to the more frequent examination point, so it is worth talking about.
Two, scene introduction, problem protruding
All right, here we go! Suppose multiple threads need to add 1 to a variable over and over again, such as the following code:
In fact, the above code is not ok, because multiple threads directly modify a data variable in this way, which is thread unsafe behavior, the data value changes do not follow the expected value change.
For example, if 20 threads each perform data++ on data once, we expect the value of data to be 20, but it’s not.
Might end up the data value is 18, or 19, is likely to be, because multi-threaded concurrent operation, just will have this kind of security issues, lead to inaccurate results.
Why is it inaccurate? That’s beyond the scope of this article, because this is something that most of you who have studied Java know about.
3. Preliminary solution: Synchronized
So, for the above code, we usually modify it to make it thread-safe by locking it:
At this point, the code is thread-safe because synchronized is added, which means that each thread must attempt to increment() before entering the method. Only one thread can lock at a time, and the other threads must wait for the lock.
By doing this, we can ensure that changing the data will add up to 1 each time, and there will be no data corruption problems.
As usual! Take a look at the picture below to get a feel for the effect and atmosphere of synchronized locking, which is equivalent to N threads queuing up to update that value one by one.
However, such a simple data++ operation, adding a heavy synchronized lock to solve the multithreaded concurrency problem, is a bit of overkill.
While improvements to Synchronized have been made with newer Java versions, it’s still “too heavy” to handle such simple accumulations. Synchronized can solve more complex concurrent programming scenarios and problems.
And synchronized in this scenario serializes threads. Queue up one by one, lock it, process the data, release the lock, and the next one comes in.
4. More efficient scheme: Atomic Atomic class and its underlying principle
For this simple data++ class operation, we could have done it the other way around. Java provides a series of Atomic Atomic classes, such as AtomicInteger.
It can guarantee multi-threaded concurrency safety in case of high performance concurrent update of a numerical value. Let’s look at the following code:
We look at the above code, is not very simple! Multiple threads can concurrently execute the incrementAndGet() method of AtomicInteger, which simply increments the value of data by one and returns the latest incremented value.
In this code, do not see the lock and release the lock of the bar!
In fact, the Atomic Atomic class is not using the traditional locking mechanism, but the lock-free CAS mechanism, through the CAS mechanism to ensure that multiple threads modify a number of security
So what is CAS? His full name is: Compare and Set, which means Compare first and then Set.
Without further ado, picture above!
If three threads concurrently modify an AtomicInteger, their underlying mechanism is as follows:
First, each thread fetches the current value and then performs an atomic CAS operation, which means that the CAS operation must complete on its own without being interrupted.
And then in the CAS operation, it’s going to compare and say, gee! Big brother! Is your value now the same value that I just got?
If so, Bingo! That no one changed this value, then you set me into a value after the sum of 1!
Similarly, if someone executes the CAS and finds that the value he or she obtained is not the same as the current value, the CAS will fail. After the failure, the CAS loop will continue to fetch the value again and then execute the CAS operation!
Good! Now let’s look at the whole process in the picture above:
- As a first step, we assume that the thread clicks in and then executes incrementAndGet() on AtomicInteger, which will fetch the current AtomicInteger value, which is 0.
- There are no other threads competing with him at this point! He also does not care so much, directly performs atomic CAS operation, and asks people to say: brother, your value is still 0?
- If so, no one has changed it! Great. Add me up by 1. Set it to 1. AtomicInteger becomes 1!
- Then thread 2 and thread 3 run in at the same time, and since the underlying CAS mechanism is not lock-based, they may execute incrementAndGet() simultaneously.
- Then they both get the current AtomicInteger value, which is 1
- Thread 2 then initiates the atomic CAS operation first! Note that CAS is atomic and only one thread is executing at this point!
- And then thread 2 asks: Brother, are you still worth 1? If it is, great, no one changed it. Let me change it to 2
- Ok, so AtomicInteger is now 2. ** Now thread 3 initiates the CAS operation, but it still has the same 1 in its hand.
- Thread 3 will now ask: Brother, are you still worth 1?
- ** bad news!! ** This time the value is 2 ah! Thread 3 cried, he said, that someone had changed the value in the meantime. Ok, so I’m just going to get the value again, so I get the latest value, which is 2.
- And then I do the CAS operation again, and I say, is the value now 2? Yes! Great, no one changed it, so I’m going to change it, and now AtomicInteger is 3!
This whole process, which is the principle of the so-called Atomic Atomic class, is not serialized based on locking, but on the CAS mechanism: get a value, then initiate CAS, and compare whether the value has been modified. If not, change the value! This CAS is atomic and no one will interrupt you!
With this mechanism, it is possible to modify a value in a lightweight way without requiring a heavy weight mechanism such as locking.
Java 8 optimization of CAS mechanism
But is there anything wrong with this CAS? There has to be. For example, if a large number of threads simultaneously modify an AtomicInteger, it is possible that many threads will keep spinning, entering an infinite repeating loop.
These threads keep fetching the value, then initiate the CAS operation, only to discover that the value has been changed by someone else, so they go through the next loop, fetch the value, initiate the CAS operation again, and then proceed to the next loop.
This problem can be more pronounced when a large number of threads update AtomicInteger with high concurrency, resulting in a large number of threads with empty loops, self-rotation, and poor performance and efficiency.
Java 8 has introduced a new class, LongAdder, which attempts to dramatically improve the performance of multi-threaded concurrent CAS operations by using segmenting CAS and automatic segmenting migration.
In the underlying implementation of LongAdder, there is a base value at first. At the beginning, multithreading keeps adding the value, which is the sum of base, for example, base = 5 at the beginning.
Then if the number of concurrent updates is too high, a segmented CAS mechanism is implemented, which is an internal array of cells, each of which is a number segment.
In this case, let a large number of threads separately perform CAS accumulation on values in different cells, so that the CAS calculation pressure is distributed in different Cell segment values!
This can greatly reduce the multi-thread concurrent update of the same value of the infinite loop problem, greatly improve the performance and efficiency of multi-thread concurrent update value!
In addition, it implements the automatic migration mechanism. That is, if a value of a Cell fails to perform CAS, the CAS operation is automatically performed on the value of another Cell segment.
This also solves the problem of thread spinning and waiting for CAS operation, allowing a thread to complete CAS operation as soon as possible.
Finally, if you want to get the current total sum from LongAdder, the base value and all Cell segment values will be added up and returned to you.
Six, summary & thinking
I don’t know if you have found this kind of segmentation mechanism under high concurrent access, in many places there are similar ideas! Because segmentation in high concurrency is actually a very common and frequently used concurrency optimization tool.
In our previous article on distributed locking :(high concurrency optimization practices for distributed locking in a thousand orders per second scenario!) , is also used to segment lock and automatic segment migration/merge lock a set of mechanisms, to greatly improve the concurrency performance of distributed lock.
So in fact, a lot of technology, ideas are similar to the wonderful.
END
If there is any harvest, please help to forward, your encouragement is the biggest power of the author, thank you!
A large wave of micro services, distributed, high concurrency, high availability of original series of articles is on the way
Please scan the qr code belowContinue to pay attention to:
Architecture Notes for Hugesia (ID: Shishan100)
More than ten years of EXPERIENCE in BAT architecture