冬眠的笔记
首页文章分类书单项目关于
冬眠
X

© 2026 冬眠的笔记 · 用文字记录思考,用思考改变生活

首页>文章>Java
JavaThreadLocal并发源码分析

ThreadLocal 详解

ThreadLocal 的实现原理、ThreadLocalMap 结构、内存泄漏问题以及 InheritableThreadLocal

冬眠
冬眠
专注于技术、阅读与思考
2025-11-19
发布日期
25 min read
阅读时长
浏览量
ThreadLocal 详解

什么是 ThreadLocal

当你去银行办理业务时,你会看到很多柜台,当你在某个柜台办理业务时,该柜台的业务员可以单独为你办理业务,拿到你的资料信息,而其他柜台的业务员则为他的客户办理业务,拿到他的客户的资料信息。多个柜台的业务员独立地处理自己的业务,互不干扰。

而在Java中,业务员就是Thread,而你在银行办理业务时的资料信息就是ThreadLocal。

ThreadLocal 是 Java 中的一个线程本地存储类,它为每个线程提供了独立的变量副本。每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。

打开 Thread 的源码,可以看到 Thread 类中有两个 ThreadLocal.ThreadLocalMap 类型的变量:

/*
* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals;

/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals;

threadLocals 是当前线程的 ThreadLocalMap,而 inheritableThreadLocals 是当前线程的可继承的 ThreadLocalMap。

ThreadLocal的基础用法

public class ThreadLocalExample {
    // 创建 ThreadLocal 变量
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
    public static void main(String[] args) {
        // 主线程设置值
        threadLocal.set("主线程的值");
        System.out.println("主线程获取值: " + threadLocal.get());
        
        // 创建新线程
        Thread thread1 = new Thread(() -> {
            threadLocal.set("线程1的值");
            System.out.println("线程1获取值: " + threadLocal.get());
        });
        
        Thread thread2 = new Thread(() -> {
            threadLocal.set("线程2的值");
            System.out.println("线程2获取值: " + threadLocal.get());
        });
        
        thread1.start();
        thread2.start();
        
        // 主线程的值不受影响
        System.out.println("主线程最终值: " + threadLocal.get());
    }
}

输出结果:

主线程获取值: 主线程的值
线程1获取值: 线程1的值
线程2获取值: 线程2的值
主线程最终值: 主线程的值

ThreadLocal的应用场景

ThreadLocal 有几个经典的应用场景:

  1. 数据库连接管理:每个线程都需要一个独立的数据库连接,而 ThreadLocal 可以确保每个线程都有自己的连接。
  2. 用户上下文信息:在 Web 应用中,每个请求都需要一个独立的用户上下文信息,而 ThreadLocal 可以确保每个请求都有自己的上下文信息。
  3. 日期格式化:SimpleDateFormat 是线程不安全的,而 ThreadLocal 可以确保每个线程都有自己的 SimpleDateFormat 对象。
  4. 随机数生成器:生成随机数时,每个线程都需要一个独立的随机数生成器。

数据库连接管理

这里给出一个简单的数据库连接管理示例:

public class DatabaseConnectionManager {
    private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
        @Override
        protected Connection initialValue() {
            try {
                return DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "user", "password");
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    };
    
    public static Connection getConnection() {
        return connectionHolder.get();
    }
    
    public static void closeConnection() {
        Connection conn = connectionHolder.get();
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                // 处理异常
            } finally {
                connectionHolder.remove();
            }
        }
    }
}

这里的示例真的比较简单,如果想了解一些连接池框架里是如何使用ThreadLocal的,建议去看看 HikariCP 的ConcurrentBag类,该类使用ThreadLocal( ThreadLocal<List<Object>> threadList)来缓存每个线程最近使用过的数据库连接,实现了线程隔离和连接复用。

用户上下文信息

在 Web 应用中,每个请求都需要一个独立的用户上下文信息,而 ThreadLocal 可以确保每个请求都有自己的上下文信息。

public class UserContext {
    private static final ThreadLocal<User> userHolder = new ThreadLocal<>();
    
    public static void setUser(User user) {
        userHolder.set(user);
    }
    
    public static User getCurrentUser() {
        return userHolder.get();
    }
    
    public static void clear() {
        userHolder.remove();
    }
}

定义好 UserContext 之后,可以在Web应用中使用拦截器来设置用户上下文信息:

public class UserInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String userId = request.getHeader("User-Id");
        if (userId!= null) {
            User user = userService.findById(userId);
            UserContext.setUser(user);
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        UserContext.clear();                 
    }
}

这里需要注意,要在afterCompletion方法中调用UserContext.clear()方法,而不要在postHandle方法中调用。

因为postHandle方法是在请求处理完成后调用的,afterCompletion方法在请求结束后调用的。

如果当请求发生异常时,postHandle方法不会被调用,因此在afterCompletion方法中调用UserContext.clear()方法是安全的。

日期格式化

我们知道 SimpleDateFormat 是线程不安全的,而 ThreadLocal 可以确保每个线程都有自己的 SimpleDateFormat 对象,从而达到线程安全。

public class DateFormatUtil {
    private static final ThreadLocal<SimpleDateFormat> dateFormat = 
        new ThreadLocal<SimpleDateFormat>() {
            @Override
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            }
        };
    
    public static String format(Date date) {
        return dateFormat.get().format(date);
    }
    
    public static Date parse(String dateStr) throws ParseException {
        return dateFormat.get().parse(dateStr);
    }
}

随机数生成器

public class ThreadLocalRandom {
    private static final ThreadLocal<Random> random = 
        new ThreadLocal<Random>() {
            @Override
            protected Random initialValue() {
                return new Random();
            }
        };
    
    public static int nextInt(int bound) {
        return random.get().nextInt(bound);
    }
    
    public static double nextDouble() {
        return random.get().nextDouble();
    }
}

其实在JDK中,也提供了线程安全的 java.util.concurrent.ThreadLocalRandom,它是通过Thread的threadLocalRandomSeed属性来保证线程安全的,实际应用时推荐 java.util.concurrent.ThreadLocalRandom。

ThreadLocal 源码分析

数据结构

ThreadLocal 的核心是 ThreadLocalMap,这是一个定制的哈希表,专门用于存储线程本地变量。

static class ThreadLocalMap {
    // Entry 继承 WeakReference,key 是 ThreadLocal 的弱引用
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    
    // 初始容量
    private static final int INITIAL_CAPACITY = 16;
    
    // Entry 数组
    private Entry[] table;
    
    // 数组大小
    private int size = 0;
    
    // 扩容阈值
    private int threshold;
}

每个 Thread 对象都包含一个 ThreadLocalMap:

public class Thread implements Runnable {
    // ThreadLocal 相关的 Map
    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    // InheritableThreadLocal 相关的 Map
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

Thread中通过threadLocals和inheritableThreadLocals来存储线程本地变量,而这两者都是ThreadLocal中的ThreadLocalMap。

ThreadLocalMap中主要是通过Entry数组来存储信息的,Entry 的 key 是 ThreadLocal 本身,而value是实际存储的线程本地变量信息。

1749352090584.png
1749352090584.png

Entry中 key 是一个弱引用,value 是一个强引用。

这里说一下强软弱虚引用:

  1. 强引用:只要强引用存在,对象绝不会被GC回收,即使内存不足时JVM会抛出OutOfMemoryError而非回收对象
  2. 软引用:通过SoftReference类实现,内存不足时才会被回收,适合实现内存敏感缓存
  3. 弱引用:通过WeakReference类实现,对象只能存活到下一次GC,无论内存是否充足。
  4. 虚引用:通过PhantomReference类实现,必须配合ReferenceQueue使用。无法通过get()获取对象,仅用于跟踪回收活动。

由于ThreadLoalMap的key是一个弱引用,当没有强引用指向 ThreadLocal 时, Entry 的 key 会成为 null,被 GC 回收。

核心方法

set() 方法

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 如果 map 存在,直接设置值
        // 在 set 方法中会通过cleanSomeSlots来清理过期的 Entry
        // 这里的过期是指 key 为 null 的 Entry
        map.set(this, value);
    else
        // 如果 map 不存在,创建 map 并设置值
        createMap(t, value);
}

// 获取线程的 ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// 创建 ThreadLocalMap
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

get() 方法

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 从 map 中获取 Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果没有找到,返回初始值
    return setInitialValue();
}

// 设置初始值
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

// 初始值方法,可以被重写
protected T initialValue() {
    return null;
}

remove() 方法

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}
private void remove(ThreadLocal<?> key) {
    //获取entry数组
    Entry[] tab = table;
    //长度
    int len = tab.length;
    //通过key计算索引
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
            e != null;
            e = tab[i = nextIndex(i, len)]) {
                    if (e.get() == key) {
        //当前引用对象所引用的目标对象(referent)是否与参数obj相同。
        if (e.refersTo(key)) {
            //清除当前entry,expungeStaleEntry同时也会清理key为null的元素
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

ThreadLocalMap 的哈希冲突解决

ThreadLocalMap 使用开放地址法(线性探测)解决哈希冲突:

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    // 计算哈希值
    int i = key.threadLocalHashCode & (len-1);
    
    // 线性探测
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        
        if (k == key) {
            // 找到相同的 key,更新 value
            e.value = value;
            return;
        }
        
        if (k == null) {
            // key 被 GC 回收,替换过期的 Entry
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    
    // 没有找到,创建新的 Entry
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

常见的哈希冲突解决思路

哈希冲突是哈希表实现中的核心问题,当不同的键计算出相同的哈希值时就会发生冲突。主要有以下几种解决方案:

1. 链地址法(Separate Chaining) 在每个哈希桶中维护一个链表或其他数据结构,冲突的元素都存储在同一个桶的链表中,HashMap 使用的就是这种方法。

HashMap 使用链地址法 + 红黑树的混合策略:

// HashMap 的核心数据结构
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next; // 链表指针
    
    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}

// 红黑树节点
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;
    boolean red;
    // ...
}

HashMap 冲突解决流程:

计算哈希值:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

定位桶位置:

// 通过哈希值和数组长度计算桶索引
int index = (n - 1) & hash; // n 是数组长度,必须是 2 的幂

处理冲突:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    
    // 计算桶索引
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 桶为空,直接插入
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            // key 相同,更新值
            e = p;
        else if (p instanceof TreeNode)
            // 红黑树节点,使用树的插入方法
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 链表节点,遍历链表
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    // 链表末尾,插入新节点
                    p.next = newNode(hash, key, value, null);
                    // 链表长度达到阈值,转换为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // TREEIFY_THRESHOLD = 8
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 更新已存在的 key
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    // ...
}

HashMap 的优化策略:

  1. 链表转红黑树:当链表长度 ≥ 8 且数组长度 ≥ 64 时,链表转换为红黑树
  2. 红黑树转链表:当红黑树节点数 ≤ 6 时,转换回链表
  3. 扰动函数:hash ^ (hash >>> 16) 减少哈希冲突
  4. 负载因子:默认 0.75,平衡时间和空间复杂度

2. 开放地址法(Open Addressing) 当发生冲突时,按照某种规则寻找下一个空闲位置,包括线性探测、二次探测、双重哈希等,ThreadLocalMap 使用的是线性探测。

ThreadLocal 的哈希算法:

public class ThreadLocal<T> {
    // 魔数,黄金分割比例的整数表示
    private static final int HASH_INCREMENT = 0x61c88647;
    
    // 全局哈希码生成器
    private static AtomicInteger nextHashCode = new AtomicInteger();
    
    // 每个 ThreadLocal 实例的哈希码
    private final int threadLocalHashCode = nextHashCode();
    
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
}

这个魔数 0x61c88647 是黄金分割比例 (√5 - 1) / 2 的整数表示,能够在 2 的幂次方长度的数组中产生非常均匀的分布,大大减少哈希冲突。

为什么 ThreadLocalMap 选择线性探测?

  1. 冲突较少:ThreadLocal 的哈希算法设计良好,冲突概率低
  2. 内存效率:无需额外的指针存储,节省内存
  3. 缓存友好:连续内存访问,CPU 缓存命中率高
  4. 实现简单:代码简洁,易于维护

ThreadLocalMap vs HashMap 冲突解决对比

特性 ThreadLocalMap HashMap
冲突解决 开放地址法(线性探测) 链地址法 + 红黑树
时间复杂度 O(1) ~ O(n) O(1) ~ O(log n)
空间利用率 高(无额外指针开销) 中等(需要额外指针)
实现复杂度 简单 复杂
适用场景 冲突较少,内存敏感 通用场景,冲突较多

3. 再哈希法(Rehashing) 使用多个哈希函数,当一个发生冲突时使用下一个,实现复杂,但冲突概率低

4. 建立公共溢出区 将冲突的元素存储在单独的溢出区域,适用于冲突较少的场景

ThreadLocal 使用注意事项

内存泄漏问题

问题原因

  1. 弱引用机制:ThreadLocalMap 的 Entry 继承 WeakReference,key(ThreadLocal)是弱引用
  2. value 强引用:Entry 的 value 是强引用
  3. 生命周期差异:线程生命周期可能比 ThreadLocal 对象长
1749354523486.png
1749354523486.png

内存泄漏场景

public class MemoryLeakExample {
    public static void main(String[] args) {
        ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
        
        // 设置大对象
        threadLocal.set(new byte[1024 * 1024 * 10]); // 10MB
        
        // ThreadLocal 对象被回收,但 value 仍然被引用
        threadLocal = null;
        System.gc();
        
        // 此时 ThreadLocalMap 中的 Entry 的 key 为 null,但 value 仍然存在
        // 如果线程不结束,这 10MB 内存就无法回收
        
        // 正确做法:手动清理
        // threadLocal.remove();
    }
}

解决方案

  1. 手动清理:
public class SafeThreadLocalUsage {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
    public void doSomething() {
        try {
            threadLocal.set("some value");
            // 业务逻辑
        } finally {
            // 确保清理
            threadLocal.remove();
        }
    }
}
  1. 使用 try-with-resources 模式:
public class AutoCleanThreadLocal<T> implements AutoCloseable {
    private final ThreadLocal<T> threadLocal = new ThreadLocal<>();
    
    public void set(T value) {
        threadLocal.set(value);
    }
    
    public T get() {
        return threadLocal.get();
    }
    
    @Override
    public void close() {
        threadLocal.remove();
    }
}

// 使用方式
public void example() {
    try (AutoCleanThreadLocal<String> autoClean = new AutoCleanThreadLocal<>()) {
        autoClean.set("value");
        // 业务逻辑
    } // 自动调用 close() 方法
}

线程池中的问题

问题描述

线程池中的线程会被复用,ThreadLocal 变量可能会在不同任务间产生数据污染:

public class ThreadPoolProblem {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    private static final ExecutorService executor = Executors.newFixedThreadPool(2);
    
    public static void main(String[] args) {
        // 任务1:设置值但不清理
        executor.submit(() -> {
            threadLocal.set("任务1的值");
            System.out.println("任务1: " + threadLocal.get());
            // 没有调用 remove()
        });
        
        // 任务2:可能获取到任务1的值
        executor.submit(() -> {
            System.out.println("任务2: " + threadLocal.get()); // 可能输出 "任务1的值"
        });
        
        executor.shutdown();
    }
}

解决方案

  1. 任务包装器:
public class ThreadLocalTaskWrapper implements Runnable {
    private final Runnable task;
    private final List<ThreadLocal<?>> threadLocals;
    
    public ThreadLocalTaskWrapper(Runnable task, ThreadLocal<?>... threadLocals) {
        this.task = task;
        this.threadLocals = Arrays.asList(threadLocals);
    }
    
    @Override
    public void run() {
        try {
            task.run();
        } finally {
            // 清理所有 ThreadLocal
            threadLocals.forEach(ThreadLocal::remove);
        }
    }
}
  1. 自定义线程池:
public class ThreadLocalAwareThreadPool extends ThreadPoolExecutor {
    
    public ThreadLocalAwareThreadPool(int corePoolSize, int maximumPoolSize, 
                                     long keepAliveTime, TimeUnit unit, 
                                     BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }
    
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        // 清理当前线程的所有 ThreadLocal
        clearThreadLocals();
    }
    
    private void clearThreadLocals() {
        try {
            Thread currentThread = Thread.currentThread();
            Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
            threadLocalsField.setAccessible(true);
            threadLocalsField.set(currentThread, null);
            
            Field inheritableThreadLocalsField = Thread.class.getDeclaredField("inheritableThreadLocals");
            inheritableThreadLocalsField.setAccessible(true);
            inheritableThreadLocalsField.set(currentThread, null);
        } catch (Exception e) {
            // 处理异常
        }
    }
}

InheritableThreadLocal

InheritableThreadLocal 是 ThreadLocal 的子类,它允许子线程继承父线程的 ThreadLocal 值。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

使用示例

public class InheritableThreadLocalExample {
    private static final InheritableThreadLocal<String> inheritableThreadLocal = 
        new InheritableThreadLocal<>();
    
    public static void main(String[] args) {
        // 父线程设置值
        inheritableThreadLocal.set("父线程的值");
        System.out.println("父线程: " + inheritableThreadLocal.get());
        
        // 创建子线程
        Thread childThread = new Thread(() -> {
            // 子线程可以获取父线程的值
            System.out.println("子线程继承的值: " + inheritableThreadLocal.get());
            
            // 子线程修改值
            inheritableThreadLocal.set("子线程修改的值");
            System.out.println("子线程修改后: " + inheritableThreadLocal.get());
        });
        
        childThread.start();
        
        try {
            childThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // 父线程的值不受影响
        System.out.println("父线程最终值: " + inheritableThreadLocal.get());
    }
}

输出结果:

父线程: 父线程的值
子线程继承的值: 父线程的值
子线程修改后: 子线程修改的值
父线程最终值: 父线程的值

需要注意的是,只有在子线程创建的时候才会从父线程继承值,之后子线程的值修改不会影响父线程,父线程值的修改也不会影响子线程。

从而引申出一个问题:线程池中的线程基本都是复用的,不会进行创建,所以在线程池中InheritableThreadLocal可能会失效。

自定义继承逻辑

public class CustomInheritableThreadLocal extends InheritableThreadLocal<Map<String, String>> {
    @Override
    protected Map<String, String> childValue(Map<String, String> parentValue) {
        // 深拷贝父线程的 Map
        return parentValue != null ? new HashMap<>(parentValue) : new HashMap<>();
    }
}

// 使用示例
public class CustomInheritanceExample {
    private static final CustomInheritableThreadLocal contextMap = 
        new CustomInheritableThreadLocal();
    
    public static void main(String[] args) {
        // 父线程设置上下文
        Map<String, String> parentContext = new HashMap<>();
        parentContext.put("userId", "123");
        parentContext.put("requestId", "req-456");
        contextMap.set(parentContext);
        
        Thread childThread = new Thread(() -> {
            Map<String, String> childContext = contextMap.get();
            System.out.println("子线程继承的上下文: " + childContext);
            
            // 子线程添加新的上下文信息
            childContext.put("childThreadId", Thread.currentThread().getName());
            System.out.println("子线程修改后的上下文: " + childContext);
        });
        
        childThread.start();
        
        try {
            childThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // 父线程的上下文不受影响
        System.out.println("父线程的上下文: " + contextMap.get());
    }
}

InheritableThreadLocal 的限制

  1. 线程池问题:线程池中的线程复用会导致继承关系混乱
  2. 性能开销:每次创建线程都需要复制父线程的 InheritableThreadLocal
  3. 内存占用:子线程会持有父线程数据的副本

线程池中使用 ThreadLocal 的最佳实践

TransmittableThreadLocal(TTL)

阿里巴巴开源的 TTL 库解决了线程池中 ThreadLocal 传递的问题:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
</dependency>

示例:

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;

public class TTLExample {
    private static final TransmittableThreadLocal<String> context = 
        new TransmittableThreadLocal<>();
    
    public static void main(String[] args) {
        // 包装线程池
        ExecutorService executor = TtlExecutors.getTtlExecutorService(
            Executors.newFixedThreadPool(2));
        
        // 主线程设置上下文
        context.set("主线程上下文");
        
        // 提交任务到线程池
        executor.submit(() -> {
            // 可以正确获取主线程的上下文
            System.out.println("线程池任务获取上下文: " + context.get());
        });
        
        executor.shutdown();
    }
}

手动传递上下文

public class ManualContextPassing {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
    public static class ContextAwareTask implements Runnable {
        private final String context;
        private final Runnable task;
        
        public ContextAwareTask(String context, Runnable task) {
            this.context = context;
            this.task = task;
        }
        
        @Override
        public void run() {
            String oldContext = threadLocal.get();
            try {
                threadLocal.set(context);
                task.run();
            } finally {
                if (oldContext != null) {
                    threadLocal.set(oldContext);
                } else {
                    threadLocal.remove();
                }
            }
        }
    }
    
    public static void submitTask(ExecutorService executor, Runnable task) {
        String currentContext = threadLocal.get();
        executor.submit(new ContextAwareTask(currentContext, task));
    }
}

ThreadLocal 性能优化

使用 withInitial() 方法

Java 8 引入了更简洁的初始化方式:

// 传统方式
private static final ThreadLocal<SimpleDateFormat> dateFormat = 
    new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

// Java 8 方式
private static final ThreadLocal<SimpleDateFormat> dateFormat = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

减少 ThreadLocal 实例数量

// 不好的做法:多个 ThreadLocal 实例
public class BadPractice {
    private static final ThreadLocal<String> userId = new ThreadLocal<>();
    private static final ThreadLocal<String> requestId = new ThreadLocal<>();
    private static final ThreadLocal<String> sessionId = new ThreadLocal<>();
}

// 好的做法:使用一个 ThreadLocal 存储上下文对象
public class GoodPractice {
    private static final ThreadLocal<RequestContext> context = new ThreadLocal<>();
    
    public static class RequestContext {
        private String userId;
        private String requestId;
        private String sessionId;
        
        // getters and setters
    }
}

预分配容量

public class OptimizedThreadLocal<T> extends ThreadLocal<T> {
    private final Supplier<T> supplier;
    
    public OptimizedThreadLocal(Supplier<T> supplier) {
        this.supplier = supplier;
    }
    
    @Override
    protected T initialValue() {
        return supplier.get();
    }
    
    // 预热方法
    public void warmUp() {
        get(); // 触发初始化
    }
}

总结

ThreadLocal 的优势

  1. 线程安全:无需同步机制,天然线程安全
  2. 性能优秀:避免了锁竞争,提高并发性能
  3. 使用简单:API 简洁,易于理解和使用
  4. 内存隔离:每个线程拥有独立的变量副本

ThreadLocal 的劣势

  1. 内存泄漏风险:需要手动清理,否则可能导致内存泄漏
  2. 线程池兼容性:在线程池环境下需要特殊处理
  3. 调试困难:线程本地变量增加了调试复杂度
  4. 内存开销:每个线程都需要存储变量副本

适用场景

  • Web 应用:存储用户会话信息、请求上下文
  • 数据库连接:管理线程级别的数据库连接
  • 日期格式化:避免 SimpleDateFormat 的线程安全问题
  • 随机数生成:为每个线程提供独立的随机数生成器
  • 事务管理:存储事务上下文信息
  • 分布式链路追踪中的TraceId传递
  • Spring Security中的SecurityContext存储
  • 多数据源切换场景

最佳实践

  1. 及时清理:使用完毕后立即调用 remove() 方法
  2. 异常安全:在 finally 块中进行清理
  3. 线程池注意:在线程池环境下使用 TTL 或手动传递上下文
  4. 合理使用:避免存储大对象,减少内存占用
  5. 监控内存:定期检查内存使用情况,防止内存泄漏

文章标签

JavaThreadLocal并发源码分析
自定义线程池工厂类
上一篇

自定义线程池工厂类

2025-11-19

Java 原子类详解
下一篇

Java 原子类详解

2025-11-19

冬眠

冬眠

博主

专注于技术、阅读与思考。在这里记录学习、思考与生活。

116
文章
2
分类
关注我
系列:Java 线程池

第 1 篇,共 3 篇

已是第一篇

下一篇

自定义线程池工厂类

文章目录

目录

  • 什么是 ThreadLocal
  • ThreadLocal的基础用法
  • ThreadLocal的应用场景
  • ThreadLocal 源码分析
  • ThreadLocal 使用注意事项
  • InheritableThreadLocal
  • 线程池中使用 ThreadLocal 的最佳实践
  • ThreadLocal 性能优化
  • 总结

相关文章

查看更多
AbstractQueuedSynchronizer (AQS) 详解

AbstractQueuedSynchronizer (AQS) 详解

2025-11-19 · 23 min read

CountDownLatch 源码分析及面试题

CountDownLatch 源码分析及面试题

2025-11-19 · 20 min read

自定义线程池工厂类

自定义线程池工厂类

2025-11-19 · 4 min read