Understanding ThreadLocal Implementation and Memory Management
Core Concept of ThreadLocal
ThreadLocal provides thread-local variables where each thread maintains its own copy of stored data, ensuring thread safety. Alternative approaches for thread safety include local stack variables or synchronization mechanisms. ThreadLocal also facilitates data sharing across method calls within the same thread.
The internal ThreadLocalMap class stores the data, using weak references as keys (allowing multiple ThreadLocal instances) to prevent memory leaks. Strong references would prevent garbage collection, while values contain the actual stored data.
// Strong reference example
MyThreadLocal<String> myLocal = new MyThreadLocal<>();
myLocal.retrieve();
// Weak reference usage internally
new MyThreadLocal<Integer>().retrieve();
Each Thread instance contains a ThreadLocalMap field called threadLocals, meaning every thread has its own storage space.
Essentially, each thread owns a unique ThreadLocalMap containing multiple <ThreadLocal instance, variable value> pairs.
Practical Applications
ThreadLocal excels when specific information needs to be accessible throughout a thread's execution lifecycle, such as storing user session data, allowing access to user IDs or other contextual information across different methods.
Source Code Analysis
Collision Handling Strategy
Unlike HashMap's chaining approach, ThreadLocal uses nextIndex() and prevIndex() methods to probe forward and backward for available slots.
Index Navigation Methods
These methods handle circular array traversal:
private static int nextIndex(int currentIndex, int arrayLength) {
return ((currentIndex + 1 < arrayLength) ? currentIndex + 1 : 0);
}
private static int prevIndex(int currentIndex, int arrayLength) {
return ((currentIndex - 1 >= 0) ? currentIndex - 1 : arrayLength - 1);
}
Set Operation Implementation
- Calculate target index position
- If position is empty, insert directly
- If occupied, traverse forward - replace value if key matches
- Continue until finding empty slot if no expired entries exist
- If expired entries exist, perform replacement and cleanup
public void assignValue(T newValue) {
Thread currentThread = Thread.currentThread();
StorageMap map = retrieveStorage(currentThread);
if (map != null) {
map.assign(this, newValue);
} else {
initializeStorage(currentThread, newValue);
}
}
private void assign(MyThreadLocal<?> keyRef, Object newValue) {
Slot[] slots = storageArray;
int length = slots.length;
int index = keyRef.localHashCode & (length - 1);
for (Slot current = slots[index];
current != null;
current = slots[index = nextIndex(index, length)]) {
MyThreadLocal<?> storedKey = current.extractKey();
if (storedKey == keyRef) {
current.data = newValue;
return;
}
if (storedKey == null) {
substituteExpiredSlot(keyRef, newValue, index);
return;
}
}
slots[index] = new Slot(keyRef, newValue);
int newSize = ++elementCount;
if (!cleanupAdjacentSlots(index, newSize) && newSize >= capacityThreshold)
resizeStorage();
}
substituteExpiredSlot Method
Handles expired entry replacement and cleanup operations (finding the first expired slot before empty space as cleanup starting point):
cleanupStart: Starting position for garbage collection
expiredPosition: Location of the expired slot to replace
private void substituteExpiredSlot(MyThreadLocal<?> keyRef, Object newValue,
int expiredPosition) {
Slot[] slots = storageArray;
int length = slots.length;
Slot current;
int cleanupStart = expiredPosition;
for (int i = prevIndex(expiredPosition, length);
(current = slots[i]) != null;
i = prevIndex(i, length)) {
if (current.extractKey() == null)
cleanupStart = i;
}
for (int i = nextIndex(expiredPosition, length);
(current = slots[i]) != null;
i = nextIndex(i, length)) {
MyThreadLocal<?> currentKey = current.extractKey();
if (currentKey == keyRef) {
current.data = newValue;
slots[i] = slots[expiredPosition];
slots[expiredPosition] = current;
if (cleanupStart == expiredPosition)
cleanupStart = i;
cleanupAdjacentSlots(removeExpiredEntries(cleanupStart), length);
return;
}
if (currentKey == null && cleanupStart == expiredPosition)
cleanupStart = i;
}
slots[expiredPosition].data = null;
slots[expiredPosition] = new Slot(keyRef, newValue);
if (cleanupStart != expiredPosition)
cleanupAdjacentSlots(removeExpiredEntries(cleanupStart), length);
}
Garbage Collection Mechanisms
removeExpiredEntries Method
Linear cleanup process that stops when encountering null slots. The expiredPosition parameter represents the starting cleanup point.
private int removeExpiredEntries(int expiredPosition) {
Slot[] slots = storageArray;
int length = slots.length;
slots[expiredPosition].data = null;
slots[expiredPosition] = null;
elementCount--;
Slot current;
int index;
for (index = nextIndex(expiredPosition, length);
(current = slots[index]) != null;
index = nextIndex(index, length)) {
MyThreadLocal<?> currentKey = current.extractKey();
if (currentKey == null) {
current.data = null;
slots[index] = null;
elementCount--;
} else {
int calculatedHash = currentKey.localHashCode & (length - 1);
if (calculatedHash != index) {
slots[index] = null;
while (slots[calculatedHash] != null)
calculatedHash = nextIndex(calculatedHash, length);
slots[calculatedHash] = current;
}
}
}
return index;
}
cleanupAdjacentSlots Method
Performs cleanup with n /= 2 iteration pattern where n represents the table length, continuing until n == 0. This implements linear cleanup strategy.
private boolean cleanupAdjacentSlots(int startIndex, int initialSize) {
boolean entriesRemoved = false;
Slot[] slots = storageArray;
int length = slots.length;
do {
startIndex = nextIndex(startIndex, length);
Slot current = slots[startIndex];
if (current != null && current.extractKey() == null) {
initialSize = length;
entriesRemoved = true;
startIndex = removeExpiredEntries(startIndex);
}
} while ((initialSize >>>= 1) != 0);
return entriesRemoved;
}
Resizing Operations
if (!cleanupAdjacentSlots(index, elementCount) && elementCount >= capacityThreshold)
rebuildStorage();
private void rebuildStorage() {
clearAllExpiredEntries();
if (elementCount >= capacityThreshold - capacityThreshold / 4)
expandCapacity();
}