Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding ThreadLocal in Java

Tech 2

Introduction to ThreadLocal

ThreadLocal, meaning "thread-local variable," implies that the variibles populated within a ThreadLocal belong to the current thread and are isolated from other threads. In other words, the variable is unique to the current thread. ThreadLocal creates a copy of the variable for each thread, allowing every thread to access its own internal copy.

A ThreadLocal variable is a thread-local variable. The object contained by the same ThreadLocal has different copies in different threads. There are a few key points to note:

  • Because each thread has its own instance copy, and that copy can only be used by the current thread, it is named "ThreadLocal."
  • Since each thread has its own instance copy that is inaccessible to other threads, there is no issue of sharing among multiple threads.

ThreadLocal provides thread-local instances. The difference between ThreadLocal and ordinary variables is that each thread using the variable initializes a completely independent instance copy. ThreadLocal variables are typically modified with private static. When a thread terminates, all ThreadLocal instance copies associated with it can be garbage collected.

Overall, ThreadLocal is suitable for scenarios where each thread needs its own independent instance, and that instance needs to be used across multiple methods—essentially, scenarios where variables are isolated between threads but shared across methods or classes.

ThreadLocal vs. Synchronized

ThreadLocal is actually a variable bound to a thread. Both ThreadLocal and Synchronized are used to resolve multi-threaded concurrent access.

However, there is a fundamental difference between ThreadLocal and Synchronized:

  1. Synchronized is used for data sharing between threads, whereas ThreadLocal is used for data isolation between threads.
  2. Synchronized utilizes a locking mechanism to ensure that a variable or code block can only be accessed by one thread at a time. ThreadLocal, on the other hand, provides a copy of the variable for each thread, so that each thread accesses a different object at any given time, thereby isolating data sharing among multiple threads. Synchronized does the exact opposite; it enables data sharing when multiple threads communicate.

To understand ThreadLocal in a nutshell: ThreadLocal acts as the key for a specific Entry in the ThreadLocalMap collection, which is an attribute of the current thread (Entry(threadLocal, value)). Although the threadLocal key is identical across different threads, the ThreadLocalMap owned by each thread is unique. This means that the same ThreadLocal (key) corresponds to different stored values in different threads, achieving the goal of variable isolation between threads. However, within the same thread, the memory address of this value remains consistent.

Code Example

public class ThreadLocalIsolationDemo {

    private static ThreadLocal<String> threadLocalData = ThreadLocal.withInitial(() -> null);

    private static void displayValue(String threadIdentifier) {
        // Print the value of the local variable in the current thread's local memory
        System.out.println(threadIdentifier + " : " + threadLocalData.get());
        // Clear the local variable from local memory
        threadLocalData.remove();
    }

    public static void main(String[] args) throws InterruptedException {
        Thread taskA = new Thread(() -> {
            threadLocalData.set("Data_A");
            displayValue("Thread-A");
            // Print the local variable after removal
            System.out.println("Thread-A after removal: " + threadLocalData.get());
        });

        taskA.start();
        Thread.sleep(1000);

        Thread taskB = new Thread(() -> {
            threadLocalData.set("Data_B");
            displayValue("Thread-B");
            System.out.println("Thread-B after removal: " + threadLocalData.get());
        });

        taskB.start();
    }
}

Output:

Thread-A : Data_A
Thread-A after removal: null
Thread-B : Data_B
Thread-B after removal: null

From this example, we can observe that the two threads retrieve their own stored variables independently, and there is no interference or mix-up in their variable access.

Internal Implementation of ThreadLocal

The set() Method

public void assignValue(T val) {
    // 1. Get the current thread
    Thread currentThread = Thread.currentThread();
    // 2. Get the ThreadLocalMap attribute from the thread.
    // If the map is not null, update the value; otherwise, create the map and assign the value.
    ThreadLocalMap localMap = retrieveMap(currentThread);
    if (localMap != null) {
        localMap.assignValue(this, val);
    } else {
        // Initialize the ThreadLocalMap and assign the value
        initializeMap(currentThread, val);
    }
}

As shown in the code above, when ThreadLocal's set method is invoked, it first fetches the current thread and retrieves its ThreadLocalMap attribute. If the map is not null, it updates the value directly; if the map is null, it instantiates the ThreadLocalMap and initializes the value.

So, what exactly is ThreadLocalMap, and how does createMap work? Let's take a closer look:

static class ThreadLocalMap {
    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object). Note that null keys (i.e., entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from the table. Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object entryValue;

        Entry(ThreadLocal<?> key, Object val) {
            super(key);
            entryValue = val;
        }
    }
}

It is evident that ThreadLocalMap is an inner static class of ThreadLocal. Its structure primarily uses Entry to store data, which inherits from WeakReference. Inside the Entry, ThreadLocal is used as the key, and the value we set is used as the value.

// Internal method of ThreadLocal
void initializeMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

// ThreadLocalMap constructor
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int index = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[index] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

The get() Method

public T retrieveValue() {
    // 1. Get the current thread
    Thread currentThread = Thread.currentThread();
    // 2. Get the current thread's ThreadLocalMap
    ThreadLocalMap localMap = retrieveMap(currentThread);
    // 3. If the map data is not null
    if (localMap != null) {
        // 3.1 Get the value stored in the ThreadLocalMap
        ThreadLocalMap.Entry entry = localMap.retrieveEntry(this);
        if (entry != null) {
            @SuppressWarnings("unchecked")
            T result = (T) entry.entryValue;
            return result;
        }
    }
    // If the data is null, initialize it. The initialization result stores the ThreadLocal as the key and null as the value in the ThreadLocalMap
    return initializeDefaultValue();
}

private T initializeDefaultValue() {
    T val = initialValue();
    Thread currentThread = Thread.currentThread();
    ThreadLocalMap localMap = retrieveMap(currentThread);
    if (localMap != null) {
        localMap.assignValue(this, val);
    } else {
        initializeMap(currentThread, val);
    }
    return val;
}

The remove() Method

The remove() method is crucial for preventing memory leaks. It removes the entry associated with the current thread from the ThreadLocalMap, ensuring that the thread-local variable is properly garbage collected when no longer needed.

Relationship Between Thread, ThreadLocal, and ThreadLocalMap

From this diagram, we can intuitively see that ThreadLocalMap is actually an attribute of the Thread class, while ThreadLocal is a utility class that manages and maintains this ThreadLocalMap attribute.

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.