The weak reference object of the ThreadLocal instance is stored as the key in the ThreadLocalMap, and the value added by the set method is the value in the ThreadLocalMap. It provides thread-local variables that are guaranteed to belong to the current thread.
attribute
private final int threadLocalHashCode = nextHashCode();
// To compute the hash value of threadLocal, incrementing each object
private static AtomicInteger nextHashCode =
new AtomicInteger();
// The golden section number makes hashes more uniform
private static final int HASH_INCREMENT = 0x61c88647;
// Add up by cas
private static int nextHashCode(a) {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
Copy the code
set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
}
// Get the threadLocalMap in the current thread
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// Initialize a threadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code
We get the currentThread reference from thread.currentthread (), and return the member variable threadLocals of the Thread instance directly from getMap. Each thread has a ThreadLocal ThreadLocalMap with ThreadLocal binding.
ThreadLocalMap
ThreadLocalMap is a static inner class of ThreadLocal. When a thread has multiple ThreadLocal’s, it needs a container to manage multiple ThreadLocal’s. ThreadLocalMap is used to manage multiple ThreadLocal’s in a thread.
static class ThreadLocalMap {
// The storage structure of the key-value pair inherits from the weak reference
static class Entry extends WeakReference<ThreadLocal<? >>{
/** The value associated with this ThreadLocal. */
Object value;
// threadLocal is wrapped as a weak reference for the keyEntry(ThreadLocal<? > k, Object v) {super(k); value = v; }}/** * The initial capacity -- MUST be a power of two. */
private static final int INITIAL_CAPACITY = 16;
/** * The table, resized as necessary. * table.length MUST always be a power of two. */
private Entry[] table;
/** * The number of entries in the table. */
private int size = 0;
/** * The next size value at which to resize. */
private int threshold; // Default to 0
}
Copy the code
A ThreadLocalMap is a simple Map structure. The bottom layer is an array with an initial size and a threshold size. The elements of the array are entries, and the key of the Entry is a reference to the ThreadLocal. Value is the value of a ThreadLocal.
set
private void set(ThreadLocal
key, Object value) {
Entry[] tab = table;
int len = tab.length;
/ / calculate the index
int i = key.threadLocalHashCode & (len-1);
// Obtain the entry corresponding to the index if it is not empty
for(Entry e = tab[i]; e ! =null;
// Use linear detection to resolve hash conflictse = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// If it is not empty and the keys are equal
if (k == key) {
e.value = value;
return;
}
// If the key is empty, gc is dropped
if (k == null) {
replaceStaleEntry(key, value, i);
return; }}// If it is empty, add an entry directly
tab[i] = new Entry(key, value);
int sz = ++size;
// Do heuristic garbage cleaning to clean up useless entries
if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code
expungeStaleEntry
private int expungeStaleEntry(int staleSlot) {
// 1. Set the current dirty entry to NULL,value to NULL, and size, that is, the number of dirty entries is reduced by one
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
// Loop back the index until an entry = null is found, exit, and return the index
for(i = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();// During this process, dirty entries are cleared and set to NULL for convenient GC
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// The main effect here is that the open address method is used, so the deleted element is one of the multiple conflicting elements, which needs to be done on the following elements
// Processing, which can be simply understood as moving the following elements forward
// Why do you do this? Basically, when you're looking for an element with an open address, you stop looking when you hit null, and you start with k==null
// Set the entry to null. If you do not move the entry, the next element will never be accessed
int h = k.threadLocalHashCode & (len - 1);
if(h ! = i) { tab[i] =null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while(tab[h] ! =null) h = nextIndex(h, len); tab[h] = e; }}}return i;
}
Copy the code
The expungeStaleEntry() method is used to help with garbage collection. According to the source code, we can see that both get and set methods may trigger the cleanup method expungeStaleEntry(), So normally you don’t run out of memory but if you don’t call get and set you may run out of memory. Get into the habit of calling remove() when you’re not using it anymore to speed up garbage collection and avoid running out of memory.
ThreadLocal memory leak
When a ThreadLocal does not have a strong external reference, GC is collected, and the key value in the ThreadLocalMap becomes null, and the Entry is referenced by the ThreadLocalMap object. If the threadLocalMap object is referenced by the Thread object, the value object will remain in memory until the Thread terminates, causing a memory leak that needs to be removed manually after the ThreadLocal variable is used.