ConcurrentHashMap (1.8) Source Profiling

Keywords: less Attribute

ConcurrentHashMap(JDK1.8) Learning Records

Read the ConcurrentHashMap series that you forgot to read, this article mainly records what you learned by looking at the ConcurrentHashMap source code.There are several main points.The article is a bit long and requires patience.

ConcurrentHashMap constructor and related properties

2. ConcurrentHashMap usage example

3. ConcurrentHashMap Follows Sample Principles

ConcurrentHashMap appears mainly because HashMap does not perform well in multithreaded situations.The following article then follows the source code to see how ConcurrentHashMap performs well in a multithreaded environment.

1. ConcurrentHashMap constructor and related properties

Constructor

The ConcurrentHashMap constructor is similar to the HashMap constructor, but slightly different when calling the tableSizeFor function in a constructor with a capacity parameter

// Parameterless constructors are also commonly used
public ConcurrentHashMap() {
}

// Constructor with Capacity Parameter
public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   // Why do you want to change the incoming capacity to the smallest integer power of 2 that is greater than 1.5 times capacity+1?
                   // Still to be understood
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
}


// Use other Map s as parameter constructors 
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
        this.sizeCtl = DEFAULT_CAPACITY;
        putAll(m);
}

public ConcurrentHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, 1);
}

public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (initialCapacity < concurrencyLevel)   // Use at least as many bins
            initialCapacity = concurrencyLevel;   // as estimated threads
        long size = (long)(1.0 + (long)initialCapacity / loadFactor);
        int cap = (size >= (long)MAXIMUM_CAPACITY) ?
            MAXIMUM_CAPACITY : tableSizeFor((int)size);
        this.sizeCtl = cap;
}

Attribute variable

// Here are some important attribute variables, many of which are similar to HashMap's

// Maximum table capacity
private static final int MAXIMUM_CAPACITY = 1 << 30;

// Default expanded table size when adding the first element without parameters
private static final int DEFAULT_CAPACITY = 16;

// The threshold for table single slot denaturation, where the chain table node tree is greater than this value before attempting denaturation
static final int TREEIFY_THRESHOLD = 8;

// Generally, when the number of nodes in a single slot decreases after expansion, the tree is converted to a chain table when the number of nodes is less than this value
static final int UNTREEIFY_THRESHOLD = 6;

// The table capacity is required to tree nodes above this value
static final int MIN_TREEIFY_CAPACITY = 64;

// Minimum processing step per thread
private static final int MIN_TRANSFER_STRIDE = 16;


private static int RESIZE_STAMP_BITS = 16;

private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;

private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

static final int MOVED     = -1; // hash for forwarding nodes
static final int TREEBIN   = -2; // hash for roots of trees
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
// A table that actually stores key-value pairs
transient volatile Node<K,V>[] table;
// References used when expanding
private transient volatile Node<K,V>[] nextTable;
// Number of key-value pairs that are temporarily table s
private transient volatile long baseCount;
// sizeCtl: 
// Greater than 0 represents 0.75 times table.length
// -1 means initializing
// -N stands for - (N-1) threads expanding
private transient volatile int sizeCtl;
// Record transfer index used when expanding
private transient volatile int transferIndex;

Static method

// The following three methods invoke some of Unsafe's methods of manipulating memory, and use reflection to get the Unsafe member variable theUnsafe can be experimented with

// Atomic elements that take the i position of a table
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
// CAS mechanism replaces element at i position
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,Node<K,V> c, Node<K,V> v) {
     return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
CAS mechanism i Location element assignment
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
    U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
2. ConcurrentHashMap usage example

Following is a general usage demonstration, and the third part uses this usage example to analyze the ConcurrentHashMap core methodology

public class MainTest {
    public static void main(String[] args) {
        ConcurrentHashMap<String,String> concurrentHashMap = new ConcurrentHashMap<>();
        // Here is the convenience to trigger ConcurrentHashMap expansion
        for(int i=0;i<13;i++){
            concurrentHashMap.put("name"+i,"fuhang");
        }
        
        concurrentHashMap.get("name1");
        
        concurrentHashMap.remove("name1");
    }
3. ConcurrentHashMap Follow Sample Principles

The ConcurrentHashMap structure is similar here to the simple structure of the table I drew when recording HashMap.

The constructor has already been pasted out above and will not be repeated here.I start directly with the put() method.

put-related methods

​ ① put(K,V)

// Exposed put method, ConcurrentHashMap calls putVal() method internally for actual processing
public V put(K key, V value) {
        return putVal(key, value, false);
}

// The actual put method, with the last parameter representing whether to replace the old value when it exists
final V putVal(K key, V value, boolean onlyIfAbsent) {
    	// Here you can see that neither key nor value can be empty in ConcurrentHashMap
    	// The key in the HashMap can be empty, and the hash value is 0 when it is empty
        if (key == null || value == null) throw new NullPointerException();
    	// Call the spread method to calculate hash, which is analyzed later
        int hash = spread(key.hashCode());
    	// BiCount records how many node s exist in a slot
        int binCount = 0;
    	// Start the for loop below to put values into the table
        for (Node<K,V>[] tab = table;;) {
            // f: Located node
            // Length of n:table
            // I: hash%n computed node index
            // fh: hash representing the node to be located
            Node<K,V> f; int n, i, fh;
            // If the tab is empty or has a length of 0, the table is initialized, mostly when the put method is first called
            if (tab == null || (n = tab.length) == 0)
                // The initTable() initialization method is analyzed later
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                // If the node of the slot is null, the direct CAS mechanism places the key, value in the i position
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                // If the hash value of the node being located is MOVED (-1), the table is expanding
                // Call the helpTransfer method to help expand capacity
                tab = helpTransfer(tab, f);
            else {
                // This means no expansion, the f-node is not empty, indicating that there is a collision
                // Put the current node at the end of the chain by zipper
                V oldVal = null;
                // First lock the head node of the chain
                synchronized (f) {
                    // Need to determine again if the node at the i-index position is a node in this loop after locking
                    // Because it is possible that after the last step of judgment, another thread execution expansion just moved to position i
                    // The node at position i then changes from f to fwd, so the positioned node needs to be determined again after locking
                    // Is it the same as before
                    if (tabAt(tab, i) == f) {
                        // Judging that hash of fh is greater than 0
                        if (fh >= 0) {
                            // Set node counter to 1
                            binCount = 1;
                            // Traverse backwards from the top node
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                // If there is a key and a node in the chain table at position i
                                // Currently given key values are equal!! Overwrite old values if onlyIfAbsent is true
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    // Break
                                    break;
                                }
                                // Or walk back
                                Node<K,V> pred = e;
                                // If you traverse to the end, append the current new value to the end
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            // If the f node is a tree node, perform a red-black tree insertion
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                // If the number of nodes at position i is not zero
                if (binCount != 0) {
                    // If the number of nodes at position i is greater than or equal to 8, try to tree all nodes at position i
                    // The length of the table within the treeifyBin needs to be greater than 64 before it can be dendrized
                    // Otherwise try to expand capacity
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    // If the old value exists, no new node is created
                    // Return the old value so long that no program is needed to perform expansion detection
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        // Here's what the resize() method does in HashMap
        addCount(1L, binCount);
        return null;
}

​ ② spread(int h)

// Method of calculating hash in ConcurrentHashMap
// HASH_BIT : 0111 1111 1111 1111 1111 1111 1111 1111
static final int spread(int h) {
    	// This is one step more than HashMap
    	// To run and run the results of (h ^ (h >>>> 16) and HASH_BIT
    	// This step is mainly to set (h ^ (h >>> 16)) to the highest position of 0, that is, to keep hash positive
    	// Because the positive and negative values of hash also represent different logical meanings
        return (h ^ (h >>> 16)) & HASH_BITS;
}

// Review of Hash calculation methods in HashMap
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

​ ③ initTable()

This method is typically called at the first putVal to initialize the table, using sizeCtl to flag the initialization state, and sizeCtl = -1 to indicate that initialization is in progress, setting sizeCtl to 0.75 times the table capacity after initialization is complete, similar to the threshold effect in HashMap.

private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
    	// The while loop determines whether the table initialization is complete
        while ((tab = table) == null || tab.length == 0) {
            // Give cup occupancy if sizeCtl is less than 0
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                // Otherwise set sizeCtl to -1, flag is initializing at this time
                try {
                    // Why should I make a judgement here for me to think about
                    if ((tab = table) == null || tab.length == 0) {
                        // If sizeCtl > 0 (Initialize ConcurrentHashMap incoming capacity) n=sc
                        // Otherwise n=DEFAULT_CAPACITY
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        // Create a Node array of length n
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        // sc set to 0.75 * n, similar to loadFactor*Cap in HashMap
                        sc = n - (n >>> 2);
                    }
                } finally {
                    // Assign sizeCtl to sc
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
}

​ ④addCount(long x, int check)

// This method is similar to resize() in HashMap
private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            // For the time being, let's not do any analysis here, but remember that s is assigned baseCount+x in the if judgment
            CounterCell a; long v; int m;
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
            s = sumCount();
        }
    	// If check>=0
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
            // while loop to determine if expansion is complete
            // When s (can be interpreted as the number of elements in the table) > sizeCtl (threshold)
            // And table!Trying to expand when = null and table.length<MAXIMUM_CAPACITY
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                // Calculate an rs based on n, also known as table.length, which is equivalent to a pre-expansion tag
                int rs = resizeStamp(n);
                // If sc is less than 0, capacity expansion is in progress
                if (sc < 0) {
                    // 1. If the sc absolutely moves 16 bits to the right (equivalent to the last 16 bits of the discarded record expansion thread), it is not equal to rs
                    // Expansion marker has changed, this expansion is not the same as before
                    // 2. If sc==rs+1 indicates the end of expansion because sc+2 is the initial value and sc-1 is the end of expansion
                    // 3. If sc == rs +MAX_RESIZER(1< 16) -1, the expansion thread has reached its maximum size
                    // 4. nt=nextTable==null indicates end of expansion
                    // 5. If transferIndex<=0 also indicates the end of expansion
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    // Otherwise, sc+1 (Number of Expanded Threads+1) will be started to expand
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    // SizeCtl is not less than 0, then set sizeCtl to (rs << RESIZE_STAMP_SHIFT) + 2)
                    // The first 16 bits of the sizeCtl value at this point indicate when the capacity size is binary before expansion begins
                    // The number of zeros before the first one from left to right is equivalent to the scene before expansion under archive.Last 16 Bit Representation
                    // How many threads are expanding, sizeCtl will become negative once set successfully
                    // The operation of + 2 here is to execute rs-1 operation when the first thread expansion is completed, which makes it easier to judge above.
                    // Invoke transfer method to start expansion
                    transfer(tab, null);
                // Count the number of nodes in the table
                s = sumCount();
            }
        }
}

​ ⑤ transfer(Node<K,V>[] tab, Node<K,V>[] nextTab)

In Code Analysis, I first introduce the logic of transfer, then add 12 elements with a map of 16 and sizeCtl of 12 to actually analyze what the extension does at one time.

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    	// n is the length of the original table, stride record processing step
        int n = tab.length, stride;
    	// time step
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        // If nexttab is empty, it is the first call to an extension
    	if (nextTab == null) {            // initiating
            try {
                // Create a new table nt that is twice as long as the original table
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                // Assign nt to nextTabl
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            // Assign nextTab to the member variable nextTable of ConcurrentHashMap
            // Assign n to the member variable transferIndex of ConcurrentHashMap
            // Recall the first if judgment in the addCount() method when sc is less than 0
            nextTable = nextTab;
            transferIndex = n;
        }
    	// Record the length of the new table with nextn
        int nextn = nextTab.length;
    	// Create a ForwardingNode, which is a subclass of Node, with a member variable inside Node[] nextTable
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
    	// This flag controls the while loop below
        boolean advance = true;
    	// Expansion End Flag
        boolean finishing = false; // to ensure sweep before committing nextTab
    	// for loop entry
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            // If advance d really enters the while loop for processing
            while (advance) {
                int nextIndex, nextBound;
                // If--i>=bound indicates that the expansion execution is complete
                if (--i >= bound || finishing)
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    // If nextIndex<=0 you can make a closing judgment
                    // Exit while loop for end detection
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    // Otherwise update transferIndex here
                    
                    // Record boundary values
                    // Index i of record start transfer
                    // Set advance d to false to exit the while loop for the following
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            // End Judgment
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                // If the end tag is true
                if (finishing) {
                    // Set member variable nextTable to null
                    // Recall if judgment in addCount() when sc is less than 0
                    nextTable = null;
                    // Set the member variable table to nextTab
                    table = nextTab;
                    // Recording sizeCtl is 0.75 * 2n
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                
                // If not, first record the value of sizeCtl with sc, then set sizeCtl to sizeCtl-1
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    // If the values restored to before the conversion are not equal at this time, return directly, indicating that there are also expansion threads running
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    // Otherwise set the finish and advance flags to true
                    finishing = advance = true;
                    // Assigning i to n is to re-enter this code block
                    i = n; // recheck before commit
                }
            }
            else if ((f = tabAt(tab, i)) == null)
               	// If the element at position i is null when expanding, set its CAS to the fwd node
                // If set successfully, advance is true, easy to enter the while loop above to decrement i
                advance = casTabAt(tab, i, null, fwd);
            else if ((fh = f.hash) == MOVED)
                // If the hash value of the i-position node is MOVED, then it is an fwd node
                // Expansion has been performed.Set advance to true, go into the while loop and decrease i
                advance = true; // already processed
            else {
                // Otherwise, f-locking is used for node transfer
                synchronized (f) {
                    // Here again to determine i f the node at position I is still f
                    // Multiple threads may execute here at the same time because locks are not available until now
                    // If one of the threads locks instantly, release the lock after the transfer of the f-node
                    // Then the other threads fail when they make the following judgment
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        if (fh >= 0) {
                            // Here I'll draw a picture to explain, like what HashMap does
                            // ln represents a header node that remains in place
                            // hn represents the head node at i+n position after expansion
                            // Here is an example of a diagram
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            // Flagged for loop 1 for easy illustration
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            // Flagged for loop 2
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            // Set nextTable i location node to ln
                            // Set nextTabl i+n location node to hn
                            // Setting the i position of the original tab to the fwd node indicates that it has been transferred
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        else if (f instanceof TreeBin) {
                            // If FH < 0, determine if it is a red-black tree root node first
                            // If the tree node is transferred
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            // The following code looks like HashMap
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            // If the number of nodes in the high and low locations is less than 6, try to untranslate
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
 }

The above is the code analysis of the transfer function, and the oral process is:

1. First get the length of the current table and mark it as n, then assign the thread a step based on the number of CPU s with a minimum processing step of 16

2. If the nextTab passed in is null, it means that it is the first expansion thread, create a 2n-size nextTab, assign it to the global variable nextTable, and assign the n-value to the global variable transferIndex

3. Use nextn to record the length of the new table (2n) and create a Forwarding Node node. Each slot node of the original table will be temporarily set to a Forwarding Node node when the slot transfer is completed.

4. Define two boolean variables advance and finish, one to control while loop adjusting index, and one to flag end of expansion

5. Enter for loop to begin expansion, first enter advance d controlled while loop to set index boundaries

6. If the index i satisfies some criteria, then if it satisfies, make a final judgment.

7. If (6) is not satisfied, assign the node in the I position of the original tab to f to determine i f f is empty. If it is empty, use CAS mechanism to set the node in the I position to ForwardingNode and advance to the result of setting the node in CAS.

8. If (7) is not satisfied, assign the hash value of the f-node to the FH variable, and I f fh==MOVED(-1), indicate that the i-location node has been transferred during the expansion process, set advance to true to facilitate the next cycle.

9. If (8) is not satisfied, f-node is transferred, then synchronized is used to lock f-node first, and then determine i f i-node is still f-node, i f it enters f-node expansion

Default capacity 16, sizeCtl (threshold) 12, flowchart when 12th element is added

Normal Chain List Node Transfer Diagram

​ ⑥ helpTransfer(Node<K,V>[] tab, Node<K,V> f)

This method primarily senses that other threads are expanding and then assists with the logic of expanding.

final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
    	// If the tab is not empty and f is the ForwardingNode node, and the nextTab of the fwd node is not empty
        if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
            // Calculate Expansion Flag
            int rs = resizeStamp(tab.length);
            while (nextTab == nextTable && table == tab &&
                   (sc = sizeCtl) < 0) {
                // This judgment has been explained before and will not be repeated here
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                // Thread + 1 will be expanded, transfer(tab,nextTab) will be called to assist with the expansion
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
}

​ ⑦ treeifyBin(Node<K,V>[] tab, int index)

This method is called when the binCount >=8 after a put key value. This method continues to judge that if table.length is less than 64, it continues to call the tryPresize() method to expand the table.If table.length is no less than 64, try to tree the node at the index index position

private final void treeifyBin(Node<K,V>[] tab, int index) {
        Node<K,V> b; int n, sc;
        if (tab != null) {
            if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
                tryPresize(n << 1);
            else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
                synchronized (b) {
                    if (tabAt(tab, index) == b) {
                        TreeNode<K,V> hd = null, tl = null;
                        for (Node<K,V> e = b; e != null; e = e.next) {
                            TreeNode<K,V> p =
                                new TreeNode<K,V>(e.hash, e.key, e.val,
                                                  null, null);
                            if ((p.prev = tl) == null)
                                hd = p;
                            else
                                tl.next = p;
                            tl = p;
                        }
                        setTabAt(tab, index, new TreeBin<K,V>(hd));
                    }
                }
            }
        }
}

​ ⑧ tryPresize(int size)

// Here is a method that will be called when table.length <64
private final void tryPresize(int size) {
    	// Set c to maximum capacity if size (n< 1) is greater than half of maximum capacity
    	// Otherwise, set c to an integer power greater than the minimum of 2 (size*1.5+1)
        int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
            tableSizeFor(size + (size >>> 1) + 1);
        int sc;
        while ((sc = sizeCtl) >= 0) {
            Node<K,V>[] tab = table; int n;
            // The following code needs to be considered and commented on later
            if (tab == null || (n = tab.length) == 0) {
                n = (sc > c) ? sc : c;
                if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                    try {
                        if (table == tab) {
                            @SuppressWarnings("unchecked")
                            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                            table = nt;
                            sc = n - (n >>> 2);
                        }
                    } finally {
                        sizeCtl = sc;
                    }
                }
            }
            else if (c <= sc || n >= MAXIMUM_CAPACITY)
                break;
            else if (tab == table) {
                // The following paragraph is a normal expansion logic, which will not be repeated here
                int rs = resizeStamp(n);
                if (sc < 0) {
                    Node<K,V>[] nt;
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
            }
        }
}

get-related methods

ConcurrentHashMap's get method is basically similar to HashMap's get method

public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

summary

This is your understanding of Concurrent HashMap after you have read the source code for a while. Write down the understanding in the form of text and graphics. There are also some points that you can't think of for a while. Then you will come back to complete the article.

Posted by iandotcom on Sun, 05 Apr 2020 02:03:26 -0700