Java ThreadLocal 笔记

  Java多线程一般都会问到 ThreadLocal,既能考数据结构,又能考线程,还能考JVM。用起来很简单,但是理解需要花费时间。


 ThreadLocal 线程本地变量/线程本地存储

只要线程是活动的并且ThreadLocal实例是可访问的,则每个线程都对其线程局部变量的副本持有隐式引用。 线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)。

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).


  每个线程都有自己的房子(线程本地变量),不和别的线程共用,没有资源争抢。 ThreadLocal是为每个线程创建一个单独的变量副本,每个线程都可以改变自己的变量副本而不影响其它线程所对应的副本。




例如,下面的类生成每个线程本地的唯一标识符。 线程的ID是在第一次调用ThreadId.get()时分配的,并且在以后的调用中保持不变。

For example, the class below generates unique identifiers local to each thread. A thread’s id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls.

import java.util.concurrent.atomic.AtomicInteger;

 public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0);

     // Thread local variable containing each thread's ID
     private static final ThreadLocal<Integer> threadId =
         new ThreadLocal<Integer>() {
             @Override protected Integer initialValue() {
                 return nextId.getAndIncrement();

     // Returns the current thread's unique ID, assigning it if necessary
     public static int get() {
         return threadId.get();



 * Creates a thread local variable. The initial value of the variable is
 * determined by invoking the {@code get} method on the {@code Supplier}.
 * @param <S> the type of the thread local's value
 * @param supplier the supplier to be used to determine the initial value
 * @return a new thread local variable
 * @throws NullPointerException if the specified supplier is null
 * @since 1.8
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);

 * Creates a thread local variable.
 * @see #withInitial(java.util.function.Supplier)
public ThreadLocal() {

新增 修改

 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
        createMap(t, value);
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 * @param  t the current thread
 * @return the map
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
 * Create the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 * @param t the current thread
 * @param firstValue value for the initial entry of the map
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);


 * Removes the current thread's value for this thread-local
 * variable.  If this thread-local variable is subsequently
 * {@linkplain #get read} by the current thread, its value will be
 * reinitialized by invoking its {@link #initialValue} method,
 * unless its value is {@linkplain #set set} by the current thread
 * in the interim.  This may result in multiple invocations of the
 * {@code initialValue} method in the current thread.
 * @since 1.5
 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)


为什么要用 ThreadLocalMap


 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
static class ThreadLocalMap {


 * Construct a new map initially containing (firstKey, firstValue).
 * ThreadLocalMaps are constructed lazily, so we only create
 * one when we have at least one entry to put in it.
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;

ThreadLocalMap Entry

 * 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 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 value;

    Entry(ThreadLocal<?> k, Object v) {
        value = v;

该哈希表中的节点继承WeakReference,使用其主要参考字段作为键(始终为ThreadLocal对象)。 请注意,空键(即entry.get() == null)表示不再引用该键,因此条目可以从表中删除。 此类条目被引用在以下代码中为“陈旧条目”。



Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"

import java.util.*;

 * @author weikeqin
 * @date 2020-06-06 18:50
public class ThreadLocalDemo {

    private static final ThreadLocal threadLocal = new ThreadLocal();

     * -Xmx10m
     * @param args
    public static void main(String[] args) {

        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
        String traceId = UUID.randomUUID().toString();
        try {


            for (int i = 0; i < 10000000; i++) {
                threadLocal.set(i); // 注释掉这行不会内存溢出
                if (i % 10000 == 0) {


            // for gc
            for (int i = 0; i < 10; i++) {


        } catch (Exception e) {


    private void method3() {

        // 使用到 traceId
        System.out.println("method3:" + threadLocal.get());

    private void method2() {

        // 使用到 traceId
        System.out.println("method2:" + threadLocal.get());

    private void method1() {

        // 使用到 traceId
        System.out.println("method1:" + threadLocal.get());


但是在web服务里由于使用线程池,会出现一个线程一直 threadLocal.set(i) 的情况,而由于ThreadLocalMap和线程的生命周期一样长,并且ThreadLocalMap使用WeakReference,WeakReference只是针对key,但是value是强引用,导致set的value一直有引用(由于线程池复用线程,线程没有死亡),最终导致内存溢出。


简而言之: threadLocals对象中的entry对象不在使用后,没有及时remove该entry对象 ,然而程序自身也无法通过垃圾回收机制自动清除,从而导致内存泄漏。


[1] java-8-api-ThreadLocal
[2] ThreadLocal内存泄漏问题
[3] 深入分析 ThreadLocal 内存泄漏问题
[4] ThreadLocal 内存泄漏问题深入分析
[5] ThreadLocal为什么会导致内存泄漏?