Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding ThreadLocal Implementation and Memory Management

Tech 2

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

  1. Calculate target index position
  2. If position is empty, insert directly
  3. If occupied, traverse forward - replace value if key matches
  4. Continue until finding empty slot if no expired entries exist
  5. 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();
}
Tags: ThreadLocal

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.