If you want to understand Java and send packets, the core is to understand CAS mechanism, because CAS can be said to be the underlying implementation principle of concurrent send packets.

Today we’ll take a look at how CAS ensures atomicity of operations and how Java8 optimizes CAS.

Synchronized: you’re overqualified

Let’s start with a few lines of code:

public class CASTest {    static int i = 0;    public static void increment() {        i++;    }}Copy the code

Increment (); increment(); increment(); increment();

Those of you who have learned how to multithread should know that this method is thread unsafe, and since i++ is not an atomic operation, it is very difficult to get 100.

And just to explain a little bit why you don’t get 100, i++, the computer has to do it in three steps. Read the value of I. 2, add I to 1.3, write the result of final I to memory.

So, (1) if thread A reads I as I = 0, (2) if thread B also reads I as I = 0. (3) THEN A increments I by 1 and writes it to memory, where I = 1. Thread B writes I to memory. Thread B writes I to memory. Thread B writes I to memory. That is, threads A and B both increment I, but the final result is 1, not 2.

So what to do? The usual solution is to lock the method as follows

public class CASTest {    static int i = 0;    public synchronized static void increment() {        i++;    }}Copy the code

With synchronized, at most one thread can enter the increment() method. This way, there will be no thread insecurity. For those of you who don’t know synchronized, read this article: The Whole idea of synchronized(from bias to heavyweight)

However, a simple increment operation adds synchronized to increment method, which seems like a bit of overuse. When multiple threads are competing for increment method, the synchronized method will be blocked outside the increment method. Finally, wake them up, and blocking/waking them up is very time consuming.

Synchronized has done a lot of tweaking since moving to JDK1.6. Yes, there are a lot of optimizations, including partial locking, lightweight locking, etc., but even with these enhancements, the overhead is still very high when multiple threads compete with each other.

CAS: Leave it to me

Is there an alternative to synchronized locking methods and keeping increment() methods thread-safe?

Can INCREMENT be thread-safe if I use the following method? The steps are as follows:

1. The thread reads the value of I from memory. If the value of I is 0, let’s call this value k.

2. Set j = k + 1.

If k is equal to the value of I in memory, it means that no other thread has changed the value of I. Then we write the value of j (now 1) to memory. If it is not equal (meaning that the value of I has been modified by another thread), instead of writing the value of j to memory, we jump back to step 1 and continue with the three operations.

Translated into code, it looks like this:

public static void increment() {    do{        int k = i;        int j = k + 1;    }while (compareAndSet(i, k, j))}Copy the code

If you simulate it, it’s thread-safe to write this way.

CompareAndSet not only reads memory, but also writes to it. This step is not thread safe.

If you can think of this, you are really thinking and simulating this process, but I want to tell you that the compareAndSet operation, it actually corresponds to a hardware operation instruction of the operating system, although there seems to be a lot of operations in it, but the operating system can ensure that it is atomic execution.

We like to call an instruction that is long in English by its abbreviation, so let’s call a compareAndSet CAS.

Therefore, CAS is thread safe. In this way, it can be said that there is no lock competition, there is no blocking and other things, so that the program can perform better.

In Java, atomic classes for CAS are also provided, for example:

  1. AtomicBoolean

  2. AtomicInteger

  3. AtomicLong

  4. AtomicReference

How do you use it? I will use the above example to modify the version, the code is as follows:

public class CASTest { static AtomicInteger i = new AtomicInteger(0); Public static void increment() {int crementAndGet(); }}Copy the code

CAS: Who secretly changed my value

Although this CAS mechanism guarantees increment(), there are still some problems. For example, when thread A is about to perform the third step, thread B increments I by 1 and then immediately decrement I by 1. Then, thread A performs the third step. Thread A thinks that no one has changed the value of I, because the value of I has not changed. And this is what we call the ABA problem.

For values of primitive types, changing the number back to the original value does not have much effect, but for reference types, it does.

Let’s do version control

To solve this ABA problem, we can introduce version control, for example, to update the version every time a thread changes the value of a reference, so that two threads hold the same reference but their versions are different, and we can prevent ABA problems. Java provides the AtomicStampedReference class to version control.

Java8 optimization of CAS.

Increment (); increment(); increment(); increment(); Every time a thread performs a third step, the value of I is changed, so the thread goes back to the first step and starts all over again.

This leads to a problem: because threads are so dense, so many people try to change the value of I that most of them fail, wasting resources in a loop.

To solve this problem, Java8 introduced a cell[] array, which works like this: If five threads increment I, which is not a lot of threads, the chances of collisions are low, let them increment CAS as usual.

However, if there are 100 threads incrementing I, the collisions will increase and these threads will be allocated to different elements of the cell array. If cell[10] has 10 elements and the initial value of the elements is 0, Then the system will divide the 100 threads into 10 groups, and each group will increment one element of the cell array, so that at the end, the value of the 10 elements of the cell array is 10, and the system will sum the value of the 10 elements, and then get 100, 2. That’s equivalent to 100 threads incrementing I 100 times.

Of course, HERE I just take an example to illustrate Java8 CAS optimization of the general principle, specific we can go to see the source code, or to search the corresponding article oh.

conclusion

It is important to understand how CAS works. It is the cornerstone of AQS, which in turn is the cornerstone of the concurrency framework. I will write an article on AQS if I have time.

Join planet Privileges

1. Play With Spring Cloud from front end to back end

2. Actual database and table middleware sharding-JDBC

3. Practical distributed task scheduling framework Elastic Job

4, configuration center Apollo combat

5. Caching of high concurrency solutions

6. More courses waiting for you to unlock, 20+ courses