Mastering Java ThreadLocal: Core Operations, Map Structure, and Practical Examples

This article explains Java's ThreadLocal mechanism, detailing its internal ThreadLocalMap structure, core set/get/remove operations with full code examples, and demonstrates how multiple ThreadLocal variables are stored per thread, providing a clear understanding of thread‑local storage in backend development.

Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
Mastering Java ThreadLocal: Core Operations, Map Structure, and Practical Examples
ThreadLocal

class contains a static inner class ThreadLocalMap, which implements the thread‑isolation mechanism. Each Thread has a ThreadLocalMap instance.

public class Thread implements Runnable {
    // ThreadLocal variable stored in Thread class
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocal Core Operations

set operation

public void set(T value) {
    // Get current thread
    Thread t = Thread.currentThread();
    // Get current thread's ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // If map exists, set value
        map.set(this, value);
    } else {
        // If map does not exist, create map and set value
        createMap(t, value);
    }
}

// ThreadLocalMap's set method
private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    // Compute index using ThreadLocal's hashCode
    int i = key.threadLocalHashCode & (len - 1);
    // Handle hash collision, linear probing
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        // If same key, update value
        if (k == key) {
            e.value = value;
            return;
        }
        // If key is null, it has been GC'd, replace entry
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // Create new Entry
    tab[i] = new Entry(key, value);
    size++;
    // Check if resize needed
    if (!cleanSomeSlots(i, size) && size >= threshold)
        rehash();
}

get operation

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T) e.value;
            return result;
        }
    }
    return setInitialValue();
}

// ThreadLocalMap's getEntry method
private Entry getEntry(ThreadLocal<?> key) {
    // Compute index
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // If found and key matches, return
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

remove operation

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

// ThreadLocalMap's remove method
private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len - 1);
    // Find and delete Entry
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            // Clear reference
            e.clear();
            // Clean up stale entry
            expungeStaleEntry(i);
            return;
        }
    }
}

It can be seen that ThreadLocal's get, set, and remove methods ultimately operate on the data stored in ThreadLocalMap.

ThreadLocalMap Structure

ThreadLocalMap contains a static inner class Entry that extends WeakReference<ThreadLocal?>. The key is a weak reference, while the value is a strong reference.

static class ThreadLocalMap {
    // Entry extends WeakReference, key is weak reference
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value; // value is strong reference
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    private static final int INITIAL_CAPACITY = 16;
    private Entry[] table;
    private int size = 0;
    private int threshold;
    ...
}

Summary

ThreadLocal stores data in a Thread's ThreadLocalMap. ThreadLocalMap holds an Entry array that can store multiple ThreadLocal objects; a single thread can hold several ThreadLocal values via the same ThreadLocalMap. Each ThreadLocal object caches only one variable value.

One thread can set multiple ThreadLocal variables:

public class ThreadLocalTest {
    // Define multiple ThreadLocal objects
    private static final ThreadLocal<String> nameThreadLocal = new ThreadLocal<>();
    private static final ThreadLocal<Integer> ageThreadLocal = new ThreadLocal<>();
    private static final ThreadLocal<User> userInfoThreadLocal = new ThreadLocal<>();

    public void setVal() {
        // Same thread can set multiple ThreadLocal values
        nameThreadLocal.set("xuanwu");
        ageThreadLocal.set(18);
        userInfoThreadLocal.set(new User("xuanwu", 18));

        // Get each ThreadLocal value
        String name = nameThreadLocal.get();
        Integer age = ageThreadLocal.get();
        User user = userInfoThreadLocal.get();
    }
}
Javabackend developmentConcurrencyThreadLocalThreadLocalMap
Xuanwu Backend Tech Stack
Written by

Xuanwu Backend Tech Stack

Primarily covers fundamental Java concepts, mainstream frameworks, deep dives into underlying principles, and JVM internals.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.