A series of problems caused by concurrentHashMap source code analysis (CAS, volatile, current package, atomic package)

I want to see the source code of concurrentHashMap, but I immediately encounter a lot of problems, such as what is CAS, such as the role of volatile. After various investigations, I finally understood a general idea, and I hope it will be helpful to everyone.

Because I was always asked about the concurrentHashMap class in interviews and answered that it can solve concurrency problems, I always felt very low, so I wanted to study it in depth.

Encounter the first problem: the difference between optimistic locking and pessimistic locking

Answer: I found a more understandable answer in the blog of the great god

1. Pessimistic lock: It is assumed that concurrency conflicts will occur, and all operations that may violate data integrity are blocked. The implementation of pessimistic locks often relies on the locking mechanism provided by the underlying layer; pessimistic locks will cause all other threads that need locks to hang, waiting for the thread holding the lock to release the lock.

2. Optimistic locking: Assuming that there will be no concurrency conflict, each time it does not lock, but assumes that there is no conflict to complete an operation, and only checks whether data integrity is violated when the operation is submitted. If it fails because of a conflict, try again until it succeeds. Optimistic locking is mostly implemented based on the data versioning mechanism. Adding a version identifier to the data, for example, in a version solution based on a database table, is generally achieved by adding a "version" field to the database table. When reading data, read out this version number together, and add one to this version number when updating later. At this time, the version data of the submitted data is compared with the current version information of the corresponding records in the database table. If the version number of the submitted data is greater than the current version number of the database table, it will be updated; otherwise, it is considered to be expired data. 

The disadvantage of optimistic locking is that it cannot solve the problem of dirty reads.

In the actual production environment, if the amount of concurrency is not large and dirty reading is not allowed, pessimistic locking can be used to solve the concurrency problem; but if the concurrency of the system is very large, pessimistic locking will bring very big performance problems, so we have to choose Optimistic locking method. The

locking mechanism has the following problems:

(1) Under multi-thread competition, locking and releasing locks will cause more context switching and scheduling delays, causing performance problems.
(2) A thread holding a lock will cause all other threads that need the lock to suspend.
(3) If a high-priority thread waits for a low-priority thread to release the lock, it will cause priority inversion, causing performance risks.


Encountered the second problem: why most variables in the class concurrentHashMap are modified with the keyword volatile

Answer: I found the answer in the blog of the Great God

Features modified by the volatile keyword:

① Make variable updates visible. As long as the assignment of the variable modified by volatile changes, other threads will be notified . If there is a copy of the same variable in the working memory of other threads, then other threads will give up this copy. The value of the variable is retrieved from the main memory again

②A memory barrier is generated to prevent instructions from being reordered. For an explanation of this, please see the following code:

package com.brickworkers;

public class VolatileTest {

    int a = 0;                 //1
    int b = 1;                 //2
    volatile int c = 2;        //3
    int d = 3;                 //4
    int e = 4; // 5

}

In the above code, because the c variable is modified with volatile, a memory barrier will be generated for this piece of code to ensure that statement 1 and statement 2 are absolutely executed when statement 3 is executed, and the execution is complete. When statement 3, statement 4 and statement 5 are definitely not executed. At the same time, it should be noted that although the execution order of statement 3 is guaranteed to be unchangeable in the above code, instruction reordering may occur in statement 1 and statement 2, statement 4 and statement 5.

Encountered the third question: what is CAS

Answer: sum up

CAS has 3 operands, the memory value V, the old expected value A, and the new value B to be modified. Modify memory value V to B if and only if expected value A and memory value V are the same, otherwise do nothing

How to ensure CAS operation in Java? At this time, the atomic package is introduced. Let's take the AtomicInteger class as an example:

public class AtomicInteger extends Number implements java.io.Serializable {  
      
    private volatile int value;  

    public final int get() {  
        return value;  
    }  
      
    public final int getAndIncrement() {  
        for (;;) {  
            int current = get();  
            int next = current + 1;  
            if (compareAndSet(current, next))  
                return current;  
        }  
    }  
      
    public final boolean compareAndSet(int expect, int update) {  
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
    }  

The compareAndSet() method in the above code is the specific operation of CAS. It uses the CAS instruction of the CPU and uses JNI to complete the non-blocking algorithm of Java.

At this point, another question (mentioned in the blog of the great god) ABA problem arises :

Because CAS needs to check whether the value has changed when operating the value, and if there is no change, update it, but if a value was originally A, became B, and then became A, then when using CAS to check, you will find that Its value didn't change, but it actually did.

The solution is these two classes under the atomic package: AtomicStampedReference/AtomicMarkableReference

A pair of <Object, Boolean> described by the AtomicMarkableReference class can atomically modify the value of Object or Boolean. This data structure is useful in some caches or state descriptions. This structure can effectively improve throughput when modifying Object/Boolean individually or simultaneously. 

The AtomicStampedReference class maintains object references with integer "stamps" that can be updated atomically. Compared with <Object,Boolean> of AtomicMarkableReference class, AtomicStampedReference maintains a data structure similar to <Object,int>, which is actually a concurrent count of objects (references) (marked version stamps stamp). But unlike AtomicInteger, this data structure can carry an object reference (Object), and can perform atomic operations on both the object and the count.

These problems are solved, it will help us to read the source code of ConcurrentHashMap


① Make variable updates visible. As long as the assignment of the variable modified by volatile changes, other threads will be notified . If there is a copy of the same variable in the working memory of other threads, then other threads will give up this copy. The value of the variable is retrieved from the main memory again

②A memory barrier is generated to prevent instructions from being reordered. For an explanation of this, please see the following code:

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324714009&siteId=291194637