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.