ThreadLocal relies on an internal class named ThreadLocalMap to manage thread-specific data. Each thread holds its own instance of ThreadLocalMap, stored as a field within the Thread object.
ThreadLocalMap uses a custom hash table with open addressing for collision resolution. The table is implemented as an array of Entry objects, where each Entry extends WeakReference<ThreadLocal<?>> to hold the key. This design allows garbage collection to reclaim ThreadLocal instanecs when no strong references exist, preventing memory leaks.
class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object val;
Entry(ThreadLocal<?> k, Object v) {
super(k);
val = v;
}
}
private static final int INITIAL_SIZE = 16;
private Entry[] entries;
private int count = 0;
private int resizeLimit;
private void computeResizeLimit(int capacity) {
resizeLimit = capacity * 2 / 3;
}
private static int advanceIndex(int idx, int cap) {
return (idx + 1 < cap) ? idx + 1 : 0;
}
private static int retreatIndex(int idx, int cap) {
return (idx - 1 >= 0) ? idx - 1 : cap - 1;
}
private void store(ThreadLocal<?> key, Object value) {
Entry[] table = entries;
int capacity = table.length;
int position = key.threadLocalHashCode & (capacity - 1);
for (Entry entry = table[position];
entry != null;
entry = table[position = advanceIndex(position, capacity)]) {
ThreadLocal<?> currentKey = entry.get();
if (currentKey == key) {
entry.val = value;
return;
}
if (currentKey == null) {
replaceObsoleteEntry(key, value, position);
return;
}
}
table[position] = new Entry(key, value);
int newCount = ++count;
if (!cleanupSomeEntries(position, newCount) && newCount >= resizeLimit)
reorganize();
}
private Entry locateEntry(ThreadLocal<?> key) {
int idx = key.threadLocalHashCode & (entries.length - 1);
Entry entry = entries[idx];
if (entry != null && entry.get() == key)
return entry;
else
return locateEntryAfterMiss(key, idx, entry);
}
private void erase(ThreadLocal<?> key) {
Entry[] table = entries;
int capacity = table.length;
int start = key.threadLocalHashCode & (capacity - 1);
for (Entry entry = table[start];
entry != null;
entry = table[start = advanceIndex(start, capacity)]) {
if (entry.get() == key) {
entry.clear();
removeObsoleteEntry(start);
return;
}
}
}
}
When ThreadLocal.set() is invoked, it retrieves the current thread's ThreadLocalMap. If the map doesn't exist, it creates one. The key's hash code is used to compute an index into the Entry array via bitwise AND with (table.length - 1). If a collision occurs, the algorithm probes subsequent slots using linear probing until an empty slot is found.
The structure can be visualized as:
- System
- Thread A
- threadLocals (ThreadLocalMap)
- Entry[]
- Entry 0: key=ThreadLocalX (weak), value=dataForThreadA
- Entry 1: key=ThreadLocalY (weak), value=otherDataForThreadA
- Entry[]
- threadLocals (ThreadLocalMap)
- Thread B
- threadLocals (ThreadLocalMap)
- Entry[]
- Entry 0: key=ThreadLocalX (weak), value=dataForThreadB
- Entry[]
- threadLocals (ThreadLocalMap)
- Thread A
ThreadLocal.get() operates similar by accessing the current thread's map and retrieving the value associated with the ThreadLocal instance as the key. The weak reference to the key enables automatic cleanup of entries when the ThreadLocal object is no longer referenced, though stale entry removal occurs during subsequent map operations like set() or remove().