summary
1. In concurrent programming, in order to control the correctness of data, we often need to use locks to ensure the execution isolation of code blocks. But in many cases, the cost of locking is too large. In some cases, our local variables are thread private, and each thread will have its own variable / quantity. At this time, we can not lock this part of data. So ThredLocal came into being.
2, As the name implies, ThredLocal is a local variable held by a thread. Variables stored in ThredLocal will not be synchronized to other threads and the main thread. All threads are invisible to other thread variables. So let's see how it works.
3. Note: light theory is not enough. In this free gift of 5 JAVA architecture project practical course and large factory interview question bank, interested can get into skirt 783802103, do not enter without foundation!
Implementation principle
ThredLocal internally implements a static class ThreadLocalMap to store variables, and uses these two member variables within the Thread class
ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
To call ThreadLocalMap to store the internal variables of the current thread.
Implementation of ThreadLocalMap
ThreadLocalMap is a map of key value structure, but it does not directly use HashMap, but implements one by itself.
Entry
Entry is the map node defined in ThreadLocalMap. It takes ThreadLocal weak reference as key and Object as value in K-V form. Weak references are used to release memory in time to avoid memory leaks.
static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
The difference between HashMap and HashMap is that they have different ways to solve the hash conflict. HashMap adopts the chain address method. In case of conflict, put the conflicting data into the same chain list, and then convert the chain list into a red black tree when the chain list reaches a certain level. The implementation of ThreadLocalMap adopts the open addressing method. It does not use the linked list structure internally, so there is no next or prev pointer inside the Entry. See the following source code for how to implement the open addressing method of ThreadLocalMap.
Member variable
// map default initialization size private static final int INITIAL_CAPACITY = 16; // An array for storing map node data private Entry[] table; // map size private int size = 0; // Critical value. When it reaches this value, it needs to be expanded private int threshold; // Capacity expansion when the critical value reaches 2 / 3 private void setThreshold(int len) { threshold = len * 2 / 3; }
The array size here is always a power of 2, for the same reason as HashMap, to reduce collisions when calculating hash offsets.
Constructor
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { // Initialize table table = new Entry[INITIAL_CAPACITY]; // Calculate the hash value of the first value int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // Create a new node table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
set method
private void set(ThreadLocal<?> key, Object value) { // Get the hash offset of ThreadLocal Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); // Traverses the array until the node is empty for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); // If the node key s are equal, we find the node we want, // Assign values to nodes if (k == key) { e.value = value; return; } // If the key of the node is empty, it means that the weak reference has recycled the key, so a wave of cleaning is needed if (k == null) { replaceStaleEntry(key, value, i); return; } } // If no corresponding node is found, the key does not exist. Create a new node tab[i] = new Entry(key, value); int sz = ++size; // Clean up. If the cleaning result fails to clean up any old nodes, // And if the array size exceeds the critical value, rehash is performed if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
Seeing this code, the implementation principle of open addressing is very clear. First, calculate the hash value of the node, find the corresponding location, and check whether the location is empty. If it is empty, insert it. If it is not empty, extend it to the next node until the empty location is found. Then our query logic is ready to come out: calculate the hash value of the node, find the corresponding location, check whether the node is the node we want to find, and if not, continue to search in the next order.
get method
private Entry getEntry(ThreadLocal<?> key) { // Calculate hash value int i = key.threadLocalHashCode & (table.length - 1); // Get the array node corresponding to the hash value Entry e = table[i]; if (e != null && e.get() == key) // If the node is not empty and the key is the same, it means that the node we are looking for is returned directly return e; else // Otherwise, keep looking back return getEntryAfterMiss(key, i, e); } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; // If the node is not empty, keep searching while (e != null) { ThreadLocal<?> k = e.get(); // If the key is the same, it means to find it and return the node if (k == key) return e; // Clear once when key is empty if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
replaceStaleEntry
// The function of this method is to clean up during set operation private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; // Slotttoexpunge is the node location where cleanup will start later int slotToExpunge = staleSlot; // Go ahead and find the first empty node and record the location for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // Start from staleSlot and traverse backward until the node is empty for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == key) { // If the key of the node is the same, replace the value e.value = value; // Swap the current node with the node on the staleSlot (put the value in the back to the front, and wait for the previous value to be recycled) tab[i] = tab[staleSlot]; tab[staleSlot] = e; // If the slotToExpunge and the staleSlot are equal, there are no nodes to clean up in the front // Clean up from the current node if (slotToExpunge == staleSlot) slotToExpunge = i; // Perform node cleanup cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // If the key is empty and the slotToExpunge and staleSlot are equal // Assign slotttoexpunge to the current node if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // If you can't find a node with the same key, // Clear the value of the current node and generate a new node tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // If the slotToExpunge and staleSlot are not equal, you need to clean up (because empty nodes are found in front) if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }
expungeStaleEntry
// Clean up nodes private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // Release current node tab[staleSlot].value = null; tab[staleSlot] = null; size--; Entry e; int i; // Loop to find the first empty node for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); // Release node when key is empty if (k == null) { e.value = null; tab[i] = null; size--; } else { // If the key is not empty, find the location where the corresponding node should be int h = k.threadLocalHashCode & (len - 1); if (h != i) { // If it is different from the current node location, // Then clean up the node and loop to find the non empty node behind and move to the front tab[i] = null; while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }
cleanSomeSlots
// This method is used to clean up empty nodes private boolean cleanSomeSlots(int i, int n) { // Mark whether any nodes are cleared boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; // If any node is empty and key is empty // This node needs to be cleared if (e != null && e.get() == null) { // Reset the value of n and mark removed as true n = len; removed = true; // Clean up the node i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; }
rehash
// When the elements of the array reach the critical value, expand the capacity private void rehash() { // Clean up all nodes first expungeStaleEntries(); // Then judge whether the critical value is expanded // Because of the first cleaning, the number here may be smaller than the previous critical value judgment // So the critical value here is determined as threshold - threshold / 4 // That is, 1 / 2 of the size if (size >= threshold - threshold / 4) resize(); } private void resize() { // Get old array, open up new array // The new array is twice the size of the old one Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; // Traverse old array for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { // If the node is not empty, judge whether the key is empty // If the key is empty, leave the node empty to help gc // If the key is not empty, put the nodes of the old array into the new array // The insertion method is consistent with the set implementation, only because it is the new array just created // There will be no data to be cleaned up, so no additional cleaning is required ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; } else { int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; }
expungeStaleEntries
// Clean up all nodes private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; // Circulation cleaning for (int j = 0; j < len; j++) { Entry e = tab[j]; if (e != null && e.get() == null) expungeStaleEntry(j); } }
About Map cleanup
The implementation of ThreadLocalMap adopts the open addressing method, and its implementation itself should be relatively simple. However, in order to facilitate GC, the internal node uses weak reference as key. Once the strong reference of the node in the array is set to null, the key of the node will be automatically recycled by GC. This makes the implementation of ThreadLocalMap extremely complex. In order to prevent memory leakage, extra cleaning has to be done during get and set methods.
Q why do I need to clean up?
If A does not clean up, the key will be recycled, but the value will still exist, and it is difficult to recycle, resulting in memory leakage.
Q why is node movement involved in cleaning?
A because in the open addressing method, the nodes with the same hash value may be arranged together continuously. When one or more nodes are recycled, there will be null nodes among the nodes with the same hash value, and when we get nodes, we will stop searching when encountering empty nodes. Therefore, if we don't do some cleaning and moving, some nodes will never be queried.
Implementation of ThreadLocal
The implementation of hashcode
After talking about the implementation principle of ThreadLocalMap, we can deeply realize how important the hashcode of ThreadLocal is. If the hash value cannot be generated in a reasonable way, resulting in uneven data distribution, the efficiency of ThreadLocalMap will be very low.
Implementation of hashcode:
private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
The hashcode implementation code of ThreadLocal is very short: the hash value of each new ThreadLocal is increased by 0x61c88647 on the basis of nextHashCode. The implementation is simple, but confusing. What is this inexplicable magic number 0x61c88647?
0x61c88647 is a gold proportion number constructed by Fibonacci. Through experimental tests, the hashcode generated by this number can largely ensure that the hash value can be evenly distributed in the array.
get
public T get() { // Get current thread Thread t = Thread.currentThread(); // Get the variable map of the current thread ThreadLocalMap map = getMap(t); if (map != null) { // Find value return ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // Return default if not found return setInitialValue(); }
set
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // Add data if map is not empty if (map != null) map.set(this, value); else // Otherwise, create a new map and put the first and data createMap(t, value); }
summary
1. Thredlocal may be a commonly used class for many people, but not everyone will pay attention to its internal implementation, but its source code is worth reading. First, its implementation code is relatively short compared with other commonly used classes, only a few hundred lines; second, its implementation is classic, classic open addressing method, classic weak reference is convenient for GC, It can be said to be a good learning material. Although I have explained the source code of the whole thredlocal completely here, the most worthy thing is its design concept and design ideas, which will play an important role in writing excellent code.
2. Note: light theory is not enough. In this free gift of 5 JAVA architecture project practical course and large factory interview question bank, interested can get into skirt 783802103, do not enter without foundation!
The text and pictures of this article come from the Internet and my own ideas. They are only for learning and communication. They have no commercial use. The copyright belongs to the original author. If you have any questions, please contact us in time
last