【Java并发】【ThreadLocal】适合初学体质的ThreadLocal

简介: ThreadLocal 是 Java 中用于实现线程本地存储(Thread-Local Storage)的核心类,它允许每个线程拥有自己独立的变量副本,从而在多线程环境中实现线程隔离,避免共享变量带来的线程安全问题。

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD

🔥 2025本人正在沉淀中... 博客更新速度++

👍 欢迎点赞、收藏、关注,跟上我的更新节奏

📚欢迎订阅专栏,专栏名《在2B工作中寻求并发是否搞错了什么》

什么是ThreadLocal?

image.png

ThreadLocal 是 Java 中用于实现线程本地存储(Thread-Local Storage)的核心类,它允许每个线程拥有自己独立的变量副本,从而在多线程环境中实现线程隔离,避免共享变量带来的线程安全问题。

  1. 线程隔离\
    每个线程通过 ThreadLocal 访问变量时,实际上操作的是线程自身内存中的独立副本,其他线程无法访问。
  2. 内部存储\
    ThreadLocal 的值存储在 线程对象(Thread)内部的 ****ThreadLocalMap 中,其本质是一个键值对结构:
    • KeyThreadLocal 实例(弱引用)。
    • Value:线程本地变量的值。

如何使用TheadLocal?

非常的简单,只要记得get、set、remove就行了。

通常声明为静态变量,以便所有实例共享同一个变量,但每个线程有独立副本。

private static ThreadLocal<Integer> threadId = new ThreadLocal<>();

set就是给当前线程赋值,get就是获取当前线程set的值,这个没话说

threadId.set(100); // 设置当前线程的值为100

int id = threadId.get(); // 获取当前线程的值

用完后,一定要记得用remove,不然会有内存泄漏的风险,至于为什么呢?原理篇会说。

try {
    threadId.set(100);
    // 执行业务逻辑
} finally {
    threadId.remove();
}

最佳实践

上下文传递\
在Web应用中传递用户会话(Session)或请求ID,避免显式传参。

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

重用非线程安全对象\
缓存 SimpleDateFormat 等对象,避免频繁创建。

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

资源隔离\
在数据库事务中绑定连接,确保同一线程内使用同一连接。

public class ConnectionHolder {
    private static final ThreadLocal<Connection> connection = new ThreadLocal<>();
    public static Connection getConnection() { 
        if (connection.get() == null) {
            connection.set(createConnection());
        }
        return connection.get();
    }
    public static void close() { 
        Connection conn = connection.get();
        if (conn != null) conn.close();
        connection.remove();
    }
}

父子线程传参数问题

当我们主线程用threadlocal,向子线程传参的时候,代码会像这样写:

public static void main(String[] args) throws InterruptedException {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.set("main thread info");
    System.out.println("main thread get: " + threadLocal.get());
    new Thread(() -> System.out.println("sub thread get: " + threadLocal.get())).start();
    threadLocal.remove();
}

输出结果,我们会发现子线程获取不到数据。

main thread get: main thread info
sub thread get: null

使用InheritableThreadLocal

public static void main(String[] args) throws InterruptedException {
    InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
    threadLocal.set("main thread info");
    System.out.println("main thread get: " + threadLocal.get());
    new Thread(() -> System.out.println("sub thread get: " + threadLocal.get())).start();
    threadLocal.remove();
}

输出结果,完美获取。

main thread get: main thread info
sub thread get: main thread info

但是通常情况下,我们是使用线程池来创建子线程,这样就有问题了。

InheritableThreadLocal 仅在子线程创建时拷贝父线程的值,后续父线程的修改对已创建的子线程无影响。下面是代码例子:

public static void main(String[] args) throws Exception {
    InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
    threadLocal.set("main value 1");
    System.out.println("main thread set value: " + threadLocal.get());
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    executorService.execute(() -> System.out.println("Task1, thread " + Thread.currentThread().getName()+  " get value : " + threadLocal.get()));

    Thread.sleep(1000);

    threadLocal.set("main value 2");
    System.out.println("main thread set value: " + threadLocal.get());
    executorService.execute(() -> System.out.println("Task2, thread " + Thread.currentThread().getName()+  " get value : " + threadLocal.get()));

    threadLocal.remove();
    executorService.shutdown();
}

输出结果,我们可以看到线程池复用了之前的线程,所以导致,

  • 线程池中的线程在第一次执行任务时,会从父线程(主线程)继承 InheritableThreadLocal 的值(即 mian value 1)。
  • 当父线程修改值为 main value 2 并提交第二个任务时,线程池中的线程已经是复用的,不会重新执行初始化逻辑,因此子线程仍然使用第一次继承的旧值。
main thread set value: main value 1
Task1, thread pool-1-thread-1 get value : main value 1
main thread set value: main value 2
Task2, thread pool-1-thread-1 get value : main value 1

TransmittableThreadLocal

TTL 的上下文传递需要显式地通过 TtlRunnable.get()TtlCallable.get() 包装任务。如果不包装,任务执行时无法捕获父线程的上下文。

public static void main(String[] args) throws Exception {
    TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
    threadLocal.set("main value 1");
    System.out.println("main thread set value: " + threadLocal.get());
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    // 需要显示的任务
    executorService.execute(TtlRunnable.get( () -> System.out.println("Task1, thread " + Thread.currentThread().getName() + " get value : " + threadLocal.get())));

    Thread.sleep(1000);

    threadLocal.set("main value 2");
    System.out.println("main thread set value: " + threadLocal.get());
    executorService.execute(TtlRunnable.get( () -> System.out.println("Task2, thread " + Thread.currentThread().getName() + " get value : " + threadLocal.get())));

    threadLocal.remove();
    executorService.shutdown();
}

输出结果

main thread set value: main value 1
Task1, thread pool-1-thread-1 get value : main value 1
main thread set value: main value 2
Task2, thread pool-1-thread-1 get value : main value 2

三种对比

特性 ThreadLocal InheritableThreadLocal TransmittableThreadLocal (TTL)
数据隔离 线程私有 父子线程继承 父子线程继承 + 线程池支持
线程池兼容性 不支持 不支持 支持
数据传递方式 子线程创建时拷贝父线程值 动态传递(每次任务执行时绑定)
适用场景 单线程数据隔离 简单父子线程传递 线程池、异步任务等复杂场景

后话

这篇仅仅是简单的使用,主播对ThreadLocal的探索并没有结束。

uu们,关注点上,下篇,主播将与你们深入源码,一起研究ThreadLocal的源码实现,和内存泄漏的原理。

目录
相关文章
|
7月前
|
存储 Java
【源码】【Java并发】【ThreadLocal】适合中学者体质的ThreadLocal源码阅读
前言 下面,跟上主播的节奏,马上开始ThreadLocal源码的阅读( ̄▽ ̄)" 内部结构 如下图所示,我们可以知道,每个线程,都有自己的threadLocals字段,指向ThreadLocalMap
484 81
【源码】【Java并发】【ThreadLocal】适合中学者体质的ThreadLocal源码阅读
|
7月前
|
存储 安全 Java
【Java并发】【原子类】适合初学体质的原子类入门
什么是CAS? 说到原子类,首先就要说到CAS: CAS(Compare and Swap) 是一种无锁的原子操作,用于实现多线程环境下的安全数据更新。 CAS(Compare and Swap) 的
151 15
【Java并发】【原子类】适合初学体质的原子类入门
|
7月前
|
Java
【源码】【Java并发】【ReentrantLock】适合中学者体质的ReentrantLock源码阅读
因为本文说的是ReentrantLock源码,因此会默认,大家对AQS有基本的了解(比如同步队列、条件队列大概> 长啥样?)。 不懂AQS的小朋友们,你们好呀!也欢迎先看看这篇
143 13
【源码】【Java并发】【ReentrantLock】适合中学者体质的ReentrantLock源码阅读
|
7月前
|
缓存 安全 Java
【Java并发】【ConcurrentHashMap】适合初学体质的ConcurrentHashMap入门
ConcurrentHashMap是Java中线程安全的哈希表实现,支持高并发读写操作。相比Hashtable,它通过分段锁(JDK1.7)或CAS+synchronized(JDK1.8)实现更细粒度锁控制,提升性能与安全性。本文详细介绍其构造方法、添加/获取/删除元素等常用操作,并对比JDK1.7和1.8的区别,帮助开发者深入理解与使用ConcurrentHashMap。欢迎关注,了解更多!
400 5
【Java并发】【ConcurrentHashMap】适合初学体质的ConcurrentHashMap入门
|
6月前
|
存储 安全 Java
深入探究Java中ThreadLocal的工作原理和用途
总结起来,ThreadLocal是Java多线程编程中一个非常有用的工具,通过为每个线程分配独立的变量副本,实现线程隔离,避免资
142 9
|
7月前
|
安全 Java
【Java并发】【ArrayBlockingQueue】适合初学体质的ArrayBlockingQueue入门
什么是ArrayBlockingQueue ArrayBlockingQueue是 Java 并发编程中一个基于数组实现的有界阻塞队列,属于 java.util.concurrent 包,实现了 Bl...
186 6
【Java并发】【ArrayBlockingQueue】适合初学体质的ArrayBlockingQueue入门
|
7月前
|
监控 Java API
【Java并发】【ReentrantLock】适合初学体质的ReentrantLock入门
前言 什么是ReentrantLock? ReentrantLock 是 Java 并发包 (java.util.concurrent.locks) 中的一个类,它实现了 Lock 接口,提供了与
274 10
【Java并发】【ReentrantLock】适合初学体质的ReentrantLock入门
|
7月前
|
安全 Java
【源码】【Java并发】【ArrayBlockingQueue】适合中学者体质的ArrayBlockingQueue
前言 通过之前的学习是不是学的不过瘾,没关系,马上和主播来挑战源码的阅读 【Java并发】【ArrayBlockingQueue】适合初学体质的ArrayBlockingQueue入门 还有一件事
125 5
【源码】【Java并发】【ArrayBlockingQueue】适合中学者体质的ArrayBlockingQueue
|
7月前
|
安全 Java
【Java并发】【LinkedBlockingQueue】适合初学体质的LinkedBlockingQueue入门
前言 你是否在线程池工具类里看到过它的身影? 你是否会好奇LinkedBlockingQueue是啥呢? 没有关系,小手手点上关注,跟上主播的节奏。 什么是LinkedBlockingQueue? ...
269 1
【Java并发】【LinkedBlockingQueue】适合初学体质的LinkedBlockingQueue入门
|
8月前
|
设计模式 存储 安全
【Java并发】【AQS】适合初学者体质的AQS入门
AQS这是灰常重要的哈,很多JUC下的框架的核心,那都是我们的AQS,所以这里,我们直接开始先研究AQS。 那说到研究AQS,那我们应该,使用开始说起🤓 入门 什么是AQS? AQS(Abst
154 8
【Java并发】【AQS】适合初学者体质的AQS入门