Source code analysis of ConcurrentHashMap

Posted by lucasmontan on Sat, 15 Jan 2022 08:47:57 +0100

Source code analysis of ConcurrentHashMap (1)

Notice before reading

  1. All the contents of this article are the contents of the teacher's source code special training class. If you are interested, you can learn about a wave: https://space.bilibili.com/457326371?from=search&seid=882812707426049189
  2. This chapter requires some pre knowledge: LongAdder principle, HashMap principle (best known), red black tree, LockSupport, volatile, CAS, Unsafe
  3. Many of the above pre knowledge can be found in the teacher's open class
  4. Source code analysis based on JDK8 version

Overview of storage structure of JDK8 and JDK7

Description of ConcurrentHashMap storage structure

  1. In general, the HashMap storage structure is not much different from that of JDK8. It is still: array + linked list + red black tree.
  2. Different from the JDK8HashMap structure, there are many FWD and TreeBin nodes.
  3. Function of FWD node: in JDK8, concurrent HashMap supports concurrent capacity expansion. If a bucket is an FWD node, it means that the hash table is being expanded. The data of the current bucket has been migrated to the new table. When a writer thread sees the FWD node, it will participate in concurrent capacity expansion. When a reader thread sees the FWD node, it will query the new table.
  4. Role of TreeBin node: the agent operates the red black tree, which maintains the two-way linked list and red black tree structure.

Description of important constants

// Hash maximum
private static final int MAXIMUM_CAPACITY = 1 << 30;
// Hash table defaults
private static final int DEFAULT_CAPACITY = 16;
// The default concurrency level, which is left over from JDK7, is basically not used in JDK8
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// Load factor, which is final here, can be modified in HashMap
private static final float LOAD_FACTOR = 0.75f;
// The threshold value for converting a linked list into a red black tree. Treeing may occur when the length of the linked list reaches 8
static final int TREEIFY_THRESHOLD = 8;
// The threshold value for the red black tree to be converted into a linked list. When the number of elements in the red black tree is equal to 6, the red black tree may be degraded into a linked list
static final int UNTREEIFY_THRESHOLD = 6;
// Joint tree_ Threshold controls whether bucket bits are trealized. Only when the length of the table array reaches 64 and the length of the linked list of a bucket is greater than or equal to 8 can they be truly trealized
static final int MIN_TREEIFY_CAPACITY = 64;
// The minimum step size of thread migration data controls the minimum interval of thread migration tasks
private static final int MIN_TRANSFER_STRIDE = 16;
// Related to capacity expansion, an identification stamp generated during capacity expansion calculation
private static int RESIZE_STAMP_BITS = 16;
// 65535 indicates the maximum number of threads for concurrent capacity expansion
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// Capacity expansion correlation
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
// When the hash value of the Node node is - 1, it indicates that the current Node is an FWD Node
static final int MOVED     = -1;
// When the hash value of the Node node is - 2, it indicates that the current Node has been trealized and the current Node is a TreeBin object. The TreeBin object agent operates the red black tree
static final int TREEBIN   = -2;
// Conversion to binary is 31 bit 1. A negative number can be obtained by bit and operation. In short, a value (negative number) can be changed into a positive number through this value and bit operation, but the converted positive number is not necessarily the absolute value of the original value.
static final int HASH_BITS = 0x7fffffff;
// Number of logical cores in the current computer
static final int NCPU = Runtime.getRuntime().availableProcessors();
// Reference to Unsafe object
private static final sun.misc.Unsafe U;
// Represents the offset of the sizecl attribute's memory address in the ConcurrentHashMap
private static final long SIZECTL;
// Represents the offset of the memory address of the TRANSFERINDEX attribute in the ConcurrentHashMap
private static final long TRANSFERINDEX;
// Represents the offset of the BASECOUNT property from the memory address in the ConcurrentHashMap
private static final long BASECOUNT;
// Represents the offset of the CELLSBUSY property's memory address in the ConcurrentHashMap
private static final long CELLSBUSY;
// Represents the offset of the CELLVALUE property from the memory address in CounterCell
private static final long CELLVALUE;
// Represents the offset address of the first element of the array
private static final long ABASE;
// Table array unit addressing is used. For example, to access the fifth element of table array, ABASE+(5*scale) is normally required. It can be changed to 5 < < ashift after bit operation
private static final int ASHIFT;

Description of important member variables

// Hash table, the length must be the power of 2
transient volatile Node<K,V>[] table;
// During the capacity expansion, the new table in the capacity expansion will be assigned to nextTable to keep the reference. After the capacity expansion, it will be set to null
private transient volatile Node<K,V>[] nextTable;
// The baseCount in the LongAdder. When there is no competition or the current LongAdder is locked, the increment is accumulated to the baseCount
private transient volatile long baseCount;
// Cellsbusy in LongAdder, 0 indicates that the current LongAdder object is unlocked, and 1 indicates locking
private transient volatile int cellsBusy;
// For the cells array in LongAdder, when the baseCount competes, the cells array will be created. The thread will get its own cell by calculating the hash value and accumulate the increment to the specified cell.
private transient volatile CounterCell[] counterCells;
/*
 * sizeCtl < 0 
 * 1. -1 Indicates that the current table is initializing (a thread is creating a table array), and the current thread is waiting
 * 2. Indicates that the current map is being expanded. The high 16 bits represent the identification stamp of the expansion, and the low 16 bits represent the number of threads currently participating in concurrent expansion (1+nThread)
 * sizeCtl = 0,Indicates that default is used when creating a table array_ Capability is the size
 * sizeCtl > 0
 * 1. If the table is not initialized, it indicates the initialization size
 * 2. If the table has been initialized, it indicates the trigger threshold for the next capacity expansion
 */
private transient volatile int sizeCtl;
// During capacity expansion, the current progress is recorded. All threads need to allocate interval tasks from this variable to execute their own tasks
private transient volatile int transferIndex;

Important internal class resolution (only properties, not methods for the time being)

Node

1. Source code
  static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
2. Attribute description
  1. Hash: the hash value calculated by the key after perturbation operation, which is used for hash addressing.
  2. Key: the key at the time of put
  3. val: value at put
  4. next: used to form a linked list

ForwardingNode(FWD)

1. Source code

static final class ForwardingNode<K,V> extends Node<K,V> {
        final Node<K,V>[] nextTable;
}
2. Attribute description
  1. nextTable: references a new hash table

TreeNode

1. Source code

static final class TreeNode<K,V> extends Node<K,V> {
        TreeNode<K,V> parent; 
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    
        boolean red;
2. Attribute description
  1. Parent: the parent node of the current node
  2. Left: left child node
  3. Right: right child node
  4. prev: previous node
  5. Red: boolean type, indicating the color of the node. true is red and false is black
  6. Other attributes: inherited from Node

TreeBin

1. Source code

static final class TreeBin<K,V> extends Node<K,V> {
        TreeNode<K,V> root;
        volatile TreeNode<K,V> first;
        volatile Thread waiter;
        volatile int lockState;
        static final int WRITER = 1;
        static final int WAITER = 2; 
        static final int READER = 4; 
}

2. Attribute description

  1. Root: the root node of the red black tree
  2. first: the head node of the linked list
  3. waiter: waiting thread (current lockState is read lock state)
  4. lockState: lock state (write lock state, read lock state)
  5. WRITER: constant 1, indicating write lock status
  6. WAITER: constant 2, wait state (must be a write thread waiting)
  7. READER: constant 4, indicating write lock status (each read thread will + 4)

Non core method parsing of ConcurrentHashMap (auxiliary method)

spread method

1. Source code

static final int spread(int h) {
	return (h ^ (h >>> 16)) & HASH_BITS;
}

2. Method description

The hash value of Node class is not the hash value of key, but the hash value of key is obtained through the perturbation function. This method is the perturbation function.

tabAt method

1. Source code

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);
}

2. Method description

Use Unsafe to obtain the element in the Node array that specifies the subscript position (Unsafe is used for performance reasons).

casTabAt method

1. Source code

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);
}

2. Method description

Through CAS, set the value to the position indicated in the tab array.

setTabAt method

1. Source code

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. Method description

Adds an element to the specified index of the specified array.

resizeStamp method

1. Source code

static final int resizeStamp(int n) {
	return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}

2. Method description

Calculate the unique identification stamp for capacity expansion, which identifies the length of the array. When a thread participates in capacity expansion, it must get this identification stamp. Only when the identification stamps are consistent can it participate in capacity expansion.

Construction method

1. Source code

    public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }

2. Method description

Initialize sizeCtl to indicate capacity.

initTable method

1. Source code

    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        // Determine whether the hash table is initialized
        while ((tab = table) == null || tab.length == 0) {
            // If the hash table is being initialized by another thread, give up the CPU or keep spinning
            if ((sc = sizeCtl) < 0)
                Thread.yield(); 
            // If the lock preemption is successful, enter the initialization hash logic    
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    // In order to prevent other threads from initializing the hash table, we need to judge again
                    if ((tab = table) == null || tab.length == 0) {
                        // If sc > 0, use sc as the capacity. If less than or equal to 0, use the default capacity of 16
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        // Assign to table
                        table = tab = nt;
                        // The trigger threshold for the next capacity expansion is 4 / 3 of the current capacity
                        sc = n - (n >>> 2);
                    }
                } finally {
                	// Release the lock and let sizeCtl represent the threshold that triggers capacity expansion
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

2. Method description

Initialize the hash table, set sizeCtl to indicate the threshold value for triggering capacity expansion, and use CAS and state variables to ensure thread safety.

Core method put analysis

1. Source code

    final V putVal(K key, V value, boolean onlyIfAbsent) {
    	// Basic judgment ensures that there is no problem with the parameters
        if (key == null || value == null) throw new NullPointerException();
        // Use the perturbation function to calculate the hash value of key
        int hash = spread(key.hashCode());
        // Initialize binCount to 0
        int binCount = 0;
        // Spin, and assign the global table to tab
        for (Node<K,V>[] tab = table;;) {
        	/*
        	 * 1. f-Represents the head node of the bucket
        	 * 2. n-Length of hash table array
        	 * 3. fh-Indicates the hash value of the bucket head node
        	 * 4. i-The subscript of the corresponding hash table
        	 */
            Node<K,V> f; int n, i, fh;
            // If the hash table is not initialized, initialize the hash table (multiple threads may enter the initTable method, but it doesn't matter, because this method is thread safe)
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            /*
        	 * 1. Hash table has been initialized
        	 * 2. (f = tabAt(tab, i = (n - 1) & hash))-Use tabAt to obtain the corresponding bucket element
        	 * 3. If the corresponding bucket element is null
        	 */ 
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                // Use the casTabAt method to set the Node to the corresponding bucket and end the spin
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   
            }
             /*
        	 * 1. Hash table has been initialized
        	 * 2. The corresponding bucket is not null
        	 * 3. (fh = f.hash) == MOVED-The corresponding bucket node is FWD node (only the hash of FWD node is - 1)
        	 */ 
            else if ((fh = f.hash) == MOVED)
            	// Participate in concurrent capacity expansion and return a new hash table
                tab = helpTransfer(tab, f);
             /*
        	 * 1. Hash table has been initialized
        	 * 2. The corresponding bucket is not null
        	 * 3. The corresponding bucket is not a FWD node
        	 * 4. The corresponding bucket position is red black tree or linked list
        	 */ 
            else {
            	// If the corresponding key already exists, the value is the original value
                V oldVal = null;
                // Using synchronized to lock the bucket node to ensure thread safety is also an embodiment of the idea of segmented locking.
                synchronized (f) {
                	// Use the tabAt method to obtain the subscript element in the hash table again and compare it with the bucket node for consistency. The reason for this is to prevent other threads from modifying before locking the bucket node, resulting in that the corresponding subscript element in the hash table is no longer f
                    if (tabAt(tab, i) == f) {
                    	// The bucket node hash value can only be: chain header node (greater than or equal to 0), TreeBin node (- 2), FWD node (- 1)
                    	// If it is the current bucket, it is a linked list structure
                        if (fh >= 0) {
                        	// Meaning of binCount in the case of linked list: when the current inserted key is different from the key of all elements, binCount represents the length of the linked list, and vice versa represents the conflict position (binCount-1)
                            binCount = 1;
                            // Spin is used to traverse the linked list and calculate binCount
                            for (Node<K,V> e = f;; ++binCount) {
                            	// key of the current element of the linked list
                                K ek;
                                // If the key of the current element in the linked list is equal to the specified key
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    // Get old value
                                    oldVal = e.val;
                                    // Replace if necessary
                                    if (!onlyIfAbsent)
                                    	// Replace the value of the conflicting node
                                        e.val = value;
                                    // End spin    
                                    break;
                                }
                               
                                // Get current node
                                Node<K,V> pred = e;
                                // If the current node is already at the end of the list
                                if ((e = e.next) == null) {
                                	 // This shows that there is no node key in the linked list, which is the same as the inserted key, and needs to be inserted at the end of the linked list
                                	 // Insert it at the end of the linked list and end the loop
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        // If the current bucket bit is a red black tree structure
                        else if (f instanceof TreeBin) {
                        	// The conflicting nodes inserted into the red black tree
                            Node<K,V> p;
                            // Setting binCount to 2 indicates that the bucket has been trealized
                            binCount = 2;
                            // Call the putTreeVal method of TreeBin to insert elements into the red black tree
                            // If there is no conflict, p is null. If there is a conflict, p has a value
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                // Coming here means there must be a conflict
                                // Get the original value
                                oldVal = p.val;
                                // Replace if necessary
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                // If it is inserted into a linked list or red black tree, you need to check whether it needs to be trealized
                if (binCount != 0) {
                	// If this condition is true, it means that the tree is required (the length of the linked list is greater than or equal to 8)
                    if (binCount >= TREEIFY_THRESHOLD)
                    	// Tree method
                        treeifyBin(tab, i);
                    // No tree is required. If the original value exists, it will be returned    
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        // Count the total amount of data in the current hash table to determine whether the expansion conditions are met
        addCount(1L, binCount);
        // Go here to show that there is no conflict and return null
        return null;
    }

2. What this method does

  1. Check whether the hash table is initialized. If not, Uninitialize the hash table
  2. If it is detected that the hash table is being expanded, participate in concurrent expansion
  3. Insert data into various structures
    1. Insert data into empty bucket
    2. The corresponding bucket object is an FWD node (participating in concurrent capacity expansion)
    3. Insert data into the linked list formed by the bucket
    4. Insert data into the red black tree (TreeBin) formed by the bucket
  4. Whether to convert the linked list to red black tree according to the conditions
  5. Should I expand the hash table

3: Variable description

  • Hash: hash value of key after perturbation function
  • binCount: 0 indicates that the addressed bucket bit is null, and 2 indicates that the bucket has been trealized
  • tab: reference table
  • f: The bucket is the head node of the
  • n: Length of hash table array
  • i: Indicates that the bucket obtained after the key is calculated through addressing is a subscript
  • fh: indicates that the bucket is the hash value of the header node
  • oldVal: old value (if any)
  • Meaning of binCount in the case of linked list: when the current inserted key is different from the key of all elements, binCount represents the length of the linked list, and vice versa represents the conflict position (binCount-1)

4: Situation description

  1. Hash table not initialized
  2. Hash table has been initialized
    1. The corresponding bucket object is null
    2. The corresponding bucket object is an FWD node
    3. A linked list is formed corresponding to the bucket position
    4. A red black tree (TreeBin) is formed corresponding to the bucket position
  3. The linked list meets the conditions for turning red black tree
  4. Check whether the capacity should be expanded
The first case: the hash table is not initialized
source code
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
code analysis

If the hash table is not initialized, call the initTable method to initialize the hash table

The second case: the corresponding bucket object is null
source code
 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
code analysis

Get the Node of the corresponding bucket. If it is null, use the casTabAt() method to try to add the Node object to the hash table. If it is successful, exit the hash table.

The third case: the corresponding bucket location object is an FWD node
source code
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
code analysis

If the hash value of the corresponding bucket node is equal to - 1, it indicates that this node is an FWD node, which indicates that the hash table is being expanded. The current thread is obliged to help with the expansion, help the expansion through helpTransfer, and return the expanded hash table.

The fourth case: a linked list is formed corresponding to the bucket position
source code
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
code analysis

If it is a linked list, there are two situations. The first is that the specified key already exists in the linked list, and the second is that it does not exist. The core is to traverse the linked list. If the specified key exists in the linked list, it will overwrite or do nothing. If it does not exist, it will be inserted into the tail of the linked list. Here binCount represents the conflict position (binCount-1) or the length of the linked list, Note that this code is locked by symchronized, and there will be no thread safety problem.

The fifth case: a red black tree is formed corresponding to the bucket position
source code
                        else if (f instanceof TreeBin) {
                            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;
                            }
                        }
code analysis

There are also two cases, similar to the linked list. In this case, it indicates that the bucket node is a TreeBin node, and the putTreeVal of the TreeBin object will be called to add elements to the red black tree. If the return value is not null, it indicates that there is a conflict, and the conflicting node is returned. Finally, determine whether to overwrite it according to onlyIfAbsent.

The sixth situation: meet the conditions of linked list to red black tree
source code
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
code analysis

Only when the corresponding bucket node is a linked list or red black tree, it will be greater than 0. If bincount > 8, it means that the length of the linked list must be greater than 8. If the conditions for converting the linked list to red black tree are met, call treeifyBin() method for tree, otherwise it will directly return oldVal.

Case 7: check whether the capacity should be expanded
source code
addCount(1L, binCount);
code analysis

This method is used separately for analysis. The function of this method is to count the total data in the current hash table and judge whether the capacity expansion conditions are met. If so, the capacity will be expanded.

Core method addCount parsing

1. Source code

    private final void addCount(long x, int check) {
    	/*
    	 * 1. as-Cells Array has the same meaning as LongAdder
    	 * 2. b-Same as base in LongAdder
    	 * 3. s-Number of hash table elements
    	 */
        CounterCell[] as; long b, s;
        // The following logic is the add method logic of LongAdder
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            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))) {
                // If you enter this method, the current thread will not participate in concurrent capacity expansion, because this method is more performance consuming
                fullAddCount(x, uncontended);
                return;
            }
            /*
             * Some threads should not participate in concurrent capacity expansion
             * 1. If the remove method calls this method, the check will be - 1 and will not participate in concurrent capacity expansion
             * 2. If the bucket bit corresponding to putVal is null and set successfully, the check will be 0
             * 3. If the chain header node during putVal is the same as the specified key, the check will be 1
             */
            if (check <= 1)
                return;
            // Calculates the number of elements in the hash table    
            s = sumCount();
        }
        /*
         * Which threads should participate in the expansion
         * 1. If putVal is a red black tree
         * 2. If it is a linked list and is not inserted into the head node of the linked list
         */
        if (check >= 0) {
        	/*
        	 * 1. tab-Reference hash table
        	 * 2. nt-nextTable
        	 * 3. sc-Capacity expansion threshold
        	 */
            Node<K,V>[] tab, nt; int n, sc;
            /*
             * Only when these conditions are met can the capacity be expanded
             * 1. If the number of elements in the hash table is greater than or equal to the capacity expansion threshold or the hash table is being expanded
             * 2. Hash table is not null (constant)
             * 3. Hash table capacity did not reach the maximum
             */
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                // Calculate capacity expansion identification stamp   
                int rs = resizeStamp(n);
                // Hash table is expanding
                if (sc < 0) {
                	/*
                	 * It is judged that it cannot participate in capacity expansion
                	 * 1. (sc >>> RESIZE_STAMP_SHIFT) != rs-Take out whether the upper 16 bits of sc are equal to the capacity expansion stamp of the current thread. If it is true, it indicates that the capacity expansion identification stamps are not equal and cannot participate in capacity expansion.
                	 * 2. sc == rs + 1-The author made a mistake and wanted to write SC = = (RS < < 16) + 1. If it is true, it means that the capacity expansion is completed.
                	 * 3.  sc == rs + MAX_RESIZERS-The author is wrong and wants to write SC = = (RS < < 16) + max_ Resizers, if true, indicates that the number of threads participating in capacity expansion has reached the maximum (65534).
                	 * 4. (nt = nextTable) == null-End of this expansion
                	 * 5. transferIndex <= 0-The global expansion progress has been less than or equal to 0, indicating that there are no steps to allocate
                	 */
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    // The capacity can be expanded. Use CAS to modify the lower 15 bits of sizeCtl (lower 16 bits + 1)    
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                // The hash table needs to be expanded, but it has not been expanded. Use CAS to modify sizectl (low 16 bits + 2) to indicate that it is currently the first expansion thread
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    // Call capacity expansion method                         
                    transfer(tab, null);
                // Recalculate the number of hash table elements after capacity expansion    
                s = sumCount();
            }
        }
    }

2. Description of method function

  1. Count the total data in the current hash table
  2. Judge whether the expansion conditions are met, and expand the capacity if they are met.

3. Method reference description

  • x: putVal method call is 1
  • check: if it is 2, it means that the bucket bit forms a red black tree. If it is not 2, it means the length of the linked list or the conflicting subscript position

4: Method variable description

  • As: the same as the Cell array in LongAdder
  • b: Same as base in LongAdder
  • s: Number of hash table elements
  • tab: hash table reference
  • nt: nextTable
  • n: Hash table length
  • sc: capacity expansion threshold
  • rs: capacity expansion identification stamp

5: Situation analysis

  1. The counter is accumulated. If it is putVal method, it is accumulated by 1
  2. Whether the expansion conditions are met
  3. Distinguish whether to expand the main thread or the auxiliary thread
  4. Auxiliary capacity expansion thread cannot participate in capacity expansion
    • Verify capacity expansion stamp
    • Is the expansion completed
    • Maximum thread size reached
    • End of capacity expansion
  5. Can participate (when all unsatisfied conditions are not met)
Case 1: counter accumulation
source code
        CounterCell[] as; long b, s;
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            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();
        }
code analysis

In the value analysis of check in putVal context, if the corresponding bucket bit is null and set successfully, it is 0, if the chain header node exists and the key is the same, it is 1, and the corresponding bucket bit is red black tree and 2.

This is the add method of LongAdder. This code has two functions

  1. Accumulation counter
  2. Filter which threads should help other threads expand

In two cases, it will only accumulate without helping other threads expand the hash table

1. The fullAddCount method is entered because it has a lot of logic and takes a long time. If the current thread needs to help other threads expand capacity, it will take a long time
2. The current thread is added to the specified cell successfully
1. If the element is deleted (negative number)
2. If the corresponding bucket bit is null when putVal is set successfully (0)
3. If the chain header node is the same as the specified key when putVal (1)

The second case: the expansion conditions are met
source code
while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) 
code analysis
  1. The first condition is true, indicating that either the hash table is expanding or the number of hash table elements is greater than the expansion threshold
  2. The second condition holds
  3. The length of the third conditional hash table must be less than the maximum capacity
The third case: distinguish whether to expand the main thread or the auxiliary thread
source code
if (sc < 0) {
    
}else if (U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2)){
    
}
Code description

SC < 0 indicates that the hash table is being expanded (the current thread is an auxiliary expansion thread). Otherwise, it means that the current thread may be the first one to expand the capacity (the main expansion thread) uses CAS to modify the sizeCtl. If the modification is successful, it is the first one to expand the capacity and then call transfer(tab, null).

The fourth case: the worker thread cannot participate in capacity expansion (identification stamp error)
source code
(sc >>> RESIZE_STAMP_SHIFT) != rs
Code description

Take out whether the upper 16 bits of sc are equal to the capacity expansion stamp of the current thread. If it is true, it indicates that the capacity expansion identification stamps are not equal and cannot participate in capacity expansion.

The fifth case: the worker thread cannot participate in the capacity expansion (after the capacity expansion is completed)
source code
sc == rs + 1
code analysis

The author made a mistake and wanted to write SC = = (RS < < 16) + 1. If it is true, it means that the capacity expansion is completed.

The sixth case: the worker thread cannot participate in the capacity expansion (the expanded thread reaches the maximum)
source code
sc == rs + MAX_RESIZERS
code analysis

The author is wrong and wants to write SC = = (RS < < 16) + max_ Resizers, if true, indicates that the number of threads participating in capacity expansion has reached the maximum (65534).

Case 7: the worker thread cannot participate in the capacity expansion (the capacity expansion ends)
source code
(nt = nextTable) == null
code analysis

End of this expansion

Case 8: can participate in capacity expansion
source code
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
    transfer(tab, nt);
Code parsing

Use CAS to update sizeCtl. If the update is successful, you can participate in capacity expansion.

Topics: Java Concurrent Programming