ThreadLocal

简介: ThreadLocal 是一种为每个线程提供独立变量副本的机制,避免了多线程间的竞争问题。通过 ThreadLocal,每个线程拥有自己的变量实例,互不干扰。其核心原理是借助 Thread 类中的 `threadLocals`,以当前 ThreadLocal 对象为键存储线程专属数据。首次访问时若未初始化,则会调用 `initialValue` 方法设置默认值。此外,线程结束时会自动清理 ThreadLocal 数据,减少内存泄漏风险。这种设计适合需要线程隔离的场景,如数据库连接、用户会话信息等。

        ThreadLocal从字面理解就是线程本地变量,貌似是一种线程私有的缓存变量的容器。为了说明ThreadLocal的特点,举个例子:比如有三个人,每个人比作一个线程,它们都需要一个袋子来装捡到的东西,也就是每个线程都希望自己有一个容器,当然,自己的捡到的东西肯定不希望和别人分享啊,也就是希望这个容器对其他人(线程)是不可见的,如果现在只有一个袋子,那怎么办?

  1. 每个人在捡东西之前一定会先抢到那个唯一的袋子,然后再捡东西,如果使用袋子的时间到了,就会马上把里面的东西消费掉,然后把袋子放到原来的地方,然后再次去抢袋子。这个方案是使用锁来避免线程竞争问题的,三个线程需要竞争同一个共享变量。
  2. 我们假设现在不是只有一个袋子了,而是有三个袋子,那么就可以给每个人安排一个袋子,然后每个人的袋子里面的对象是对其他人不可见的,这样的好处是解决了多个人竞争同一个袋子的问题。这个方案就是使用ThreadLocal来避免不必要的线程竞争的。
    大概了解了ThreadLocal,下面来看看它的使用方法:
private static class UnsafeThreadClass {
   
    private int i;
    UnsafeThreadClass(int i) {
   
        this.i = i;
    }
    int getAndIncrement() {
   
        return ++ i;
    }
    @Override
    public String toString() {
   
        return "[" + Thread.currentThread().getName() + "]" + i;
    }
}
private static ThreadLocal<UnsafeThreadClass> threadLocal = new ThreadLocal<>();
static class ThreadLocalRunner extends Thread {
   
    @Override
    public void run() {
   
        UnsafeThreadClass unsafeThreadClass = threadLocal.get();
        if (unsafeThreadClass == null) {
   
            unsafeThreadClass = new UnsafeThreadClass(0);
            threadLocal.set(unsafeThreadClass);
        }
        unsafeThreadClass.getAndIncrement();
        System.out.println(unsafeThreadClass);
    }
}

        上面的例子仅仅是为了说明ThreadLocal可以为每个线程保存一个本地变量,这个变量不会受到其他线程的干扰,你可以使用多个ThreadLocal来让线程保存多个变量,下面我们分析一下ThreadLocal的具体实现细节,首先,ThreadLocal提供的一些方法中,我们重点关注的是get、set、remove方法。

        首先,我们需要new一个ThreadLocal对象,那么ThreadLocal的构造函数做了什么呢?

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

        很遗憾它什么都没做,那么初始化的过程势必是在首次set的时候做的,我们来看一下set方法的细节:

public void set(T value) {
   
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

        看起来首先根据当前线程获取到了一个ThreadLocalMap,getMap方法是做了什么?

ThreadLocalMap getMap(Thread t) {
   
    return t.threadLocals;
}

        非常的简洁,是和Thread与生俱来的,我们看一下Thread中的相关定义:

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

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

        获得了线程的ThreadLocalMap之后,如果不为null,说明不是首次set,直接set就可以了,注意key是this,也就是当前的ThreadLocal啊不是Thread。如果为空呢?说明还没有初始化,那么就需要执行createMap这个方法:

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

        没什么特别的,就是初始化线程的threadLocals,然后设定key-value。
下面分析一下get的逻辑:

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();
}

        和set一样,首先根据当前线程获取ThreadLocalMap,然后判断是否为null,如果为null,说明ThreadLocalMap还没有被初始化啊,那么就返回方法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;
}

        最后会返回null,但是会做一些初始化的工作,和set一样。在get里面,如果返回的ThreadLocalMap不为null,则说明ThreadLocalMap已经被初始化了,那么就可以正常根据ThreadLocal作为key获取了。
当线程退出时,会清理ThreadLocal,可以看下面的代码:

/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
   
    if (group != null) {
   
        group.threadTerminated(this);
        group = null;
    }
    /* Aggressively null out all reference fields: see bug 4006245 */
    target = null;
    /* Speed the release of some of these resources */
    threadLocals = null;
    inheritableThreadLocals = null;
    inheritedAccessControlContext = null;
    blocker = null;
    uncaughtExceptionHandler = null;
}

        这里做了大量“Help GC”的工作。包括我们本节所讲的threadLocals和下一小节要讲的inheritableThreadLocals都会被清理。

        如果我们想要显示的清理ThreadLocal,可以使用remove方法:

public void remove() {
   
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}
相关文章
|
Java 中间件 微服务
27个阿里 Java 开源项目,值得收藏!
大家好,这里为大家整理了阿里的Java开源项目,希望对大家有所帮助
13048 0
27个阿里 Java 开源项目,值得收藏!
|
5月前
|
人工智能 安全 调度
HiClaw 上线 Worker 模板市场,提供稳定可共享的 Agent 生产力
HiClaw支持Worker模板,沉淀标准流程并稳定复用。
530 38
|
机器学习/深度学习 人工智能 程序员
Reflexion:让AI智能体学会反思的神奇技术
想象一下AI智能体能像人类一样从错误中学习,Reflexion技术让智能体不再需要重新训练就能自我改进。本文通过一个智能体助手小R的成长故事,带你轻松理解这个改变智能体学习方式的创新技术。
597 2
|
人工智能 前端开发 测试技术
Apipost 与 Apifox 深度对比:2025全方位解析助力 API 开发的利器
本文对比了Apipost与Apifox两款API开发与管理工具在功能、使用场景及用户评价等方面的差异。Apipost在API设计、调试、文档管理、Mock服务、离线支持及AI能力方面表现更优,尤其适合大型企业级项目和高效率需求的团队。而Apifox则适用于小型项目或对功能要求较低的团队。综合来看,Apipost在多方面具备明显优势,是高效、高质量API开发的理想选择。
738 24
|
存储 分布式计算 Hadoop
HadoopCPU、内存、存储限制
【7月更文挑战第13天】
1012 14
|
机器学习/深度学习 缓存 人工智能
一文了解DeepSeek及应用场景
本文详细介绍了DeepSeek及其应用场景,涵盖了大模型的发展历程、基本原理和分类(通用与推理模型)。文章分析了DeepSeek的具体特性、性能优势、低成本训练与调用特点,以及其技术路线(如MoE、MLA架构),并与竞品进行了对比。此外,还探讨了DeepSeek在金融风控等领域的应用前景。
一文了解DeepSeek及应用场景
|
存储 数据采集 前端开发
ClkLog 实践中的挑战:如何设计和实施有效的埋点指标
前端数据埋点要怎么做才能获取到有用的数据并对运营产生积极的作用,对于首次实施埋点及数据分析的工程师来说确实是个难点。网上很多文章讲的都是方法论和理论知识,真正实践的内容比较少,我们从一个案例来描述一下埋点要如何做。
ClkLog 实践中的挑战:如何设计和实施有效的埋点指标
|
监控 搜索推荐 Java
Java 多线程最新实操技术与应用场景全解析:从基础到进阶
本文深入探讨了Java多线程的现代并发编程技术,涵盖Java 8+新特性,如CompletableFuture异步处理、Stream并行流操作,以及Reactive编程中的Reactor框架。通过具体代码示例,讲解了异步任务组合、并行流优化及响应式编程的核心概念(Flux与Mono)。同时对比了同步、CompletableFuture和Reactor三种实现方式的性能,并总结了最佳实践,帮助开发者构建高效、扩展性强的应用。资源地址:[点击下载](https://pan.quark.cn/s/14fcf913bae6)。
706 3
|
XML Java 数据库连接
Mybatis一对一,一对多关联查询
## MyBatis一对一、一对多关联查询详解 MyBatis是一款优秀的持久层框架,提供了灵活的SQL映射功能,支持复杂的数据库操作。本文将详细介绍MyBatis中一对一和一对多关联查询的实现。 ### 一对一关联查询 一对一关联关系指的是一个表中的一条记录与另一个表中的一条记录相关联。例如,一个用户有一个地址信息。 #### 数据库表设计 假设有两个表:`user`和 `address`。 ``` CREATE TABLE user ( id INT PRIMARY KEY, name VARCHAR(50) ); CREATE TABLE address
648 18
|
JSON 负载均衡 Java
SpringCloud Feign 远程调用(史上最详细讲解)
SpringCloud Feign 远程调用(史上最详细讲解)
15818 0
SpringCloud Feign 远程调用(史上最详细讲解)

热门文章

最新文章