I tell you a great secret of AtomicInteger

i++ is not a thread-safe operation because it is not an atomic operation.

So, if I want to achieve this effect similar to i++, which collection or tool class should I use?

Before JDK1.5, in order to ensure the atomicity of a certain 基本data type or 引用data type operation under multithreading , you must rely on external keywords synchronized, but this situation has changed after JDK1.5, of course you can still use synchronized To ensure atomicity, one of the thread-safe methods we are talking about here is atomic tool classes, such as AtomicInteger, AtomicBoolean, etc. These atoms classes are thread-safe tools, they are also Lock-Freeof. Let's take a look at these tools and what the concept of Lock-Free is.

Understanding AtomicInteger

AtomicInteger It is a newly added tool class in JDK 1.5, let’s first look at its inheritance relationship

AtomicInteger01

Like the wrapper class Integer int, they are inherited from the Numberclass.

AtomicInteger02

The Number class is a wrapper class for basic data types. Generally, objects related to data types inherit from the Number class.

Its inheritance system is very simple, let’s take a look at its basic properties and methods

Basic properties of AtomicInteger

There are three basic attributes of AtomicInteger

wTiyJH.png

UnsafeA sun.miscpackage following class, AtomicInteger mainly dependent on the number of native methods sun.misc.Unsafe provide guarantees atomicity operation.

Unsafe The objectFieldOffsetmethod may acquire the address in memory member property with respect to the object memory address offset. To put it simply, find the address of this variable in memory, which is convenient for subsequent operations directly through the memory address. This value isvalue

We will elaborate on this later

value Is the value of AtomicIneger.

The construction method of AtomicInteger

Continuing to look down, there are only two construction methods for AtomicInteger. One is a parameterless construction method. The default value of the parameterless construction method is 0. The parameterized construction method can specify the initial value.

AtomicInteger04

Methods in AtomicInteger

Let's talk about the methods in AtomicInteger.

Get and Set

Let's first look at the simplest get and set methods:

get() : Get the current value of AtomicInteger

set() : Set the current value of AtomicInteger

AtomicInteger atomic data can be read in the get (), set () can set the current atomic value because get () and set () are ultimately applied to the variable value, and the value is volatilemodified, so get and set are equivalent to reading and setting the memory. As shown below

AtomicInteger05

We mentioned the non-atomic operations of i++ and i++ above, we said that we can use the method in AtomicInteger to replace.

Incremental operation

AtomicInteger in the Incrementalrelevant way to meet our needs

  • getAndIncrement(): Atomically increase the current value and return the result. Equivalent i++operation.
image-20200911085857825

In order to verify whether it is thread-safe, we use the following example to test

public class TAtomicTest implements Runnable{
    
    

    AtomicInteger atomicInteger = new AtomicInteger();

    @Override
    public void run() {
    
    
        for(int i = 0;i < 10000;i++){
    
    
            System.out.println(atomicInteger.getAndIncrement());
        }
    }
    public static void main(String[] args) {
    
    

        TAtomicTest tAtomicTest = new TAtomicTest();

        Thread t1 = new Thread(tAtomicTest);
        Thread t2 = new Thread(tAtomicTest);
        t1.start();
        t2.start();
    }

}

By outputting the result, you will find that it is a thread-safe operation. You can modify the value of i, but the final result is still i-1, because the value is taken first, and then + 1, its schematic diagram is as follows.

AtomicInteger06
  • incrementAndGetIn contrast, the + 1 operation is performed first, and then the result of the increment is returned. This operation method can ensure the atomic operation of the value. As shown below
AtomicInteger07

Decremental operation

In contrast, decrement operations such as x-- or x = x-1 are also atomic. We can still use the method in AtomicInteger to replace

  • getAndDecrement: Return the int value of the current type, and then decrement the value of value. Below is the test code
class TAtomicTestDecrement implements Runnable{
    
    

    AtomicInteger atomicInteger = new AtomicInteger(20000);

    @Override
    public void run() {
    
    
        for(int i = 0;i < 10000 ;i++){
    
    
            System.out.println(atomicInteger.getAndDecrement());
        }
    }

    public static void main(String[] args) {
    
    

        TAtomicTestDecrement tAtomicTest = new TAtomicTestDecrement();

        Thread t1 = new Thread(tAtomicTest);
        Thread t2 = new Thread(tAtomicTest);
        t1.start();
        t2.start();

    }

}

The following is a schematic diagram of getAndDecrement

AtomicInteger08
  • decrementAndGet: Similarly, the decrementAndGet method is to perform the decrement operation first, and then get the value of value, the diagram is as follows
AtomicInteger09

LazySet method

Do you know that volatile has a memory barrier?

What is the memory barrier?

Memory barrier, also known as 内存栅栏memory barrier, barrier instruction, etc., is a type of synchronization barrier instruction, which is a synchronization point in the operation of random access to memory by the CPU or compiler, so that all read and write operations before this point are executed Only then can you start to perform operations after this point. It is also a technology that makes the memory state in the CPU processing unit visible to other processing units.

The CPU uses a lot of optimizations, such as the use of cache, instruction rearrangement, etc. The ultimate goal is performance, that is, when a program is executed, as long as the final result is the same, it does not matter whether the instructions are rearranged. Therefore, the execution timing of instructions is not executed sequentially, but executed out of order, which will bring many problems, which also promotes the emergence of memory barriers.

Semantically, all write operations before the memory barrier must be written to the memory; read operations after the memory barrier can obtain the result of the write operation before the synchronization barrier. Therefore, for sensitive program blocks, memory barriers can be inserted after write operations and before read operations.

The overhead of the memory barrier is very lightweight, but no matter how small it is, there is an overhead. LazySet does exactly this. It will read and write variables in the form of ordinary variables.

It can also be said: too lazy to set up barriers

AtomicInteger10

GetAndSet method

Atomically set to the given value and return the old value.

Its source code is to call the getAndSetInt method in unsafe, as shown below

AtomicInteger11

Was first circulated, and then call the getIntVolatilemethod, which I did not find in cpp, the small partners to find remember to tell me to learn about.

Loop until compareAndSwapInt returns false, which means that CAS is not updated to the new value, so var5 returns the latest memory value.

CAS method

We have often said that the CAS is actually a CompareAndSetmethod, which as the name suggests, is to compare and update the meaning of course, this is literally, literally a little deviation, in fact, people mean to compare, if satisfied then no longer be updated.

AtomicInteger12

Given above CAS Java source code level, JDK give it official explanation is that if the current value equal to the value of expect, then the atomic nature of the current value is set to update the given value , this method returns a boolean type, If it is true, it means the comparison and update succeeded, otherwise it means it failed.

CAS is also a lock-free concurrency mechanism, also known as it Lock Free, so do you think Lock Free is very big? not at all.

Below we build a locked and unlocked CASLock

class CASLock {
    
    

    AtomicInteger atomicInteger = new AtomicInteger();
    Thread currentThread = null;

    public void tryLock() throws Exception{
    
    

        boolean isLock = atomicInteger.compareAndSet(0, 1);
        if(!isLock){
    
    
            throw new Exception("加锁失败");
        }

        currentThread = Thread.currentThread();
        System.out.println(currentThread + " tryLock");

    }

    public void unlock() {
    
    

        int lockValue = atomicInteger.get();
        if(lockValue == 0){
    
    
            return;
        }
        if(currentThread == Thread.currentThread()){
    
    
            atomicInteger.compareAndSet(1,0);
            System.out.println(currentThread + " unlock");
        }
    }

    public static void main(String[] args) {
    
    

        CASLock casLock = new CASLock();

        for(int i = 0;i < 5;i++){
    
    

            new Thread(() -> {
    
    
                try {
    
    
                    casLock.tryLock();
                    Thread.sleep(10000);
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    casLock.unlock();
                }
            }).start();
        }

    }
}

In the above code, we built a CASLock, in the tryLockmethod, we first use the CAS method update, the update is not successful if an exception is thrown and the current thread is set to locking thread. In the unLockmethod, we first determine whether the current value is 0, 0 if it is the result of what we would like to see a direct return. Otherwise, it means that the current thread is still locked. Let's judge whether the current thread is a locked thread, and if it is, perform the unlock operation.

Then the compareAndSet we mentioned above can actually be parsed as the following operation

// 伪代码

// 当前值
int v = 0;
int a = 0;
int b = 1;

if(compare(0,0) == true){
    
    
  set(0,1);
}
else{
    
    
  // 继续向下执行
}

You can also take the example of buying a ticket in a life scene. You must hold a ticket to enter the scenic spot. If you have a fake ticket or a ticket that does not meet the scenic spot, it will definitely be able to be identified. You certainly can't enter the scenic spot.

Stop talking nonsense, here is a schematic diagram of compareAndSet

AtomicInteger14
  • weakCompareAndSet: Damn, I watched it several times very carefully, and found that this method of JDK1.8 is exactly the same as the compareAndSet method, cheating me. . .
AtomicInteger15

But is this really the case? No, the JDK source code is very extensive and profound, so you won't design a repetitive method. Think about it, the JDK team will not commit such a low-level team, but what is the reason?

The book "Java High Concurrency Detailed" gives us an answer

AtomicInteger16

AddAndGet

AddAndGet and getAndIncrement, getAndAdd, incrementAndGet and other methods all use the do...while + CAS operation, which is actually equivalent to a spin lock. If the CAS modification is successful, it will keep looping, and the modification failure will return. The schematic is as follows

AtomicInteger17

Dive into AtomicInteger

We discussed the specific use of AtomicInteger above. At the same time, we know that AtomicInteger relies on volatile and CAS to ensure atomicity, so let's analyze why CAS can guarantee atomicity and what is its underlying layer? What does AtomicInteger have to do with optimistic locking?

The underlying implementation principle of AtomicInteger

Let us look at this lovely compareAndSetL(CAS)way why these two lines of code to ensure the atomicity?

AtomicInteger18

We can see that this method is equivalent to calling the CAS in unsafe compareAndSwapIntmethods, we can only send into unsafe looks into implementation.

AtomicInteger19

compareAndSwapInt is sun.miscthe method, which is a nativemethod, it is the underlying C / C ++ to achieve, so we need to see the C / C ++ source code.

Do you know the awesomeness of C/C++? Using Java is playing the application and architecture, and C/C++ is playing the server and the bottom layer.

compareAndSwapInt source in jdk8u-dev/hotspot/src/share/vm/prims/unsafe.appthe path, its source is achieved

AtomicInteger20

That is Unsafe_CompareAndSwapIntthe method we have found this method

AtomicInteger21

I don't understand the C/C++ source code, but this does not prevent us from finding the key code Atomic::cmpxchg. cmpxchg is an assembly instruction of the x86 CPU architecture. Its main function is to compare and exchange operands. Let's continue to find the definition of this instruction.

We will find that corresponding to different os, the underlying implementation is different

AtomicInteger22

We found the implementation of Windows as follows

AtomicInteger23

We continue to look down, it actually defines the code on line 216, we look in

AtomicInteger24

At this time, knowledge of assembly instructions and registers is required.

The above os::is-MP()is a multiprocessing operating system interface, the following is __asm, it is a C / C ++ keywords used to call the inline assembler.

__asm assembler code is, broadly speaking is to dest, exchange_value, compare_value values are placed in the register, following LOCK_IF_MPthe general meaning of the code is

AtomicInteger25

If it is a multi-processor, it will execute lock and then perform comparison operations. Cmp wherein the comparison indicates, is represented by MP MultiProcess, jeexpressed equal jump, the L0 represents the flag.

We return to the assembly instructions above, we can see, CAS is the underlying cmpxchginstructions.

Optimistic lock

Do you have this question, why AtomicInteger can get the current value, then why is there still expectValueand valueinconsistency of it?

Because AtomicInteger just one atom of tools, it is not exclusive, it is not like synchronizedor is lockthe same as having mutually exclusive and exclusionary, remember that there are two ways to get AtomicInteger and set it? They just use volatilemodified a bit, but does not have the atomic volatile, so there may be the current value and the value of expectValue inconsistent, so there may be repeated modifications.

There are solutions to this situation for the above two, one is using synchronizedand lockand similar locking mechanism, which has the exclusive lock, which means that only one thread at a time to be modified in this way can guarantee Atomicity, but relatively expensive, this kind of lock is also called pessimistic lock. Another solution is to use 版本号or yes CAS 方法.

version number

The version number is in the data table mechanism adding a versionfield to achieve, indicates the number of data is modified, when the write operation is performed and the write was successful, version = version + 1, when the thread A to update the data, read The data will also read the version value at the same time. When submitting the update, if the version value read just now is equal to the version value in the current database, it will be updated. Otherwise, the update operation will be retried until the update is successful.

CAS method

Another way is CAS. We have used a lot of space above to introduce the CAS method. Then we think that you have a certain understanding of its operating mechanism, and we will not elaborate on its operating mechanism.

Everything has advantages and disadvantages. There is no perfect solution in the software industry but the best solution. So Optimistic Lock also has its weaknesses and defects, that is, the ABA problem.

ABA problem

The ABA problem says that if the value of a variable read for the first time is A, and when you are ready to write to A, you find that the value is still A, then in this case, it can be considered that the value of A has not been changed. ? It can be the case of A -> B -> A, but AtomicInteger doesn't think so. It only believes what it sees, and what it sees is what it sees. For example

If there is a singly linked list, as shown in the figure below

AtomicInteger26

A.next = B and B.next = null. At this time, two threads T1 and T2 respectively take out A from the singly linked list. For some special reasons, T2 changes A to B and then to A. At this time, T1 Execute the CAS method and find that the singly-linked list is still A, and the CAS method will be executed. Although the result is correct, this operation will cause some potential problems.

AtomicInteger27

At this time, it is still a singly linked list. Two threads T1 and T2 respectively take out A from the singly linked list, and then T1 changes the linked list to ACD as shown in the figure below

AtomicInteger28

At this time T2, if the memory value is still A, it will try to replace the value of A with B, because the reference of B is null, then C and D will be in a free state

AtomicInteger29

JDK 1.5 after AtomicStampedReferenceclass provides such a capability, wherein the compareAndSetmethod first checks whether the present value is equal to the expected value, the current reference standard is determined and the expected reference indicia and indicium are equal and, if all are equal, setting Atomically Is the given update value.

AtomicInteger30

Okay, the above is the Java code flow. Seeing the native, we know that we are going to cpp again. Kailu

AtomicInteger31

Simple explanation is UnsafeWrapperthat wrapper, a change in name only. Then after some JNI processing, because compareAndSwapOject compares by reference, it needs to go through C++ object-oriented conversion. The main method isatomic_compare_exchange_oop

AtomicInteger32

As you can see, familiar vocabulary has appeared again cmpxchg, that is to say, compareAndSwapOject still uses cmpxchg atomic instruction, but it has gone through a series of conversions.

postscript

A question is raised, can CAS guarantee visibility between variables? why?

There is another question, where getIntVolatileis the cpp source code of the method? How to find?

If the top bosses are interested in these two issues, welcome to communicate.

Follow the official account programmer cxuan and reply to cxuan to receive high-quality information.

I have written six PDFs myself, very hardcore, the links are as follows

I have written six PDFs myself, very hardcore, the links are as follows

I have written six PDFs myself, very hardcore, the links are as follows

cxuan has worked hard to produce four PDFs.

cxuan has two more PDFs

Guess you like

Origin blog.csdn.net/qq_36894974/article/details/108701784