ThreadLocal的使用和实现原理

本文深入讲解了ThreadLocal的工作原理及内部实现机制,包括其如何在不同线程间隔离数据,以及ThreadLocalMap的具体作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

ThreadLocal是一个将指定值储存到指定线程的类。简单的讲ThreadLocal可以指定储存数据类型,然后在不同线程中设置某个值,这个值在其他线程是不可以获取到的,只能在本线程才能获取到。平常中很少使用到ThreadLocal,但在也不少见到,比如Looper类中就有使用到它。

使用

ThreadLocal的使用不难,就是先创建ThreadLocal对象并指定要存储的类型,分别在各个线程中存放指定的值即可。

 private  ThreadLocal<String>mThreadLocal=new ThreadLocal<>();//创建ThreadLocal对象并指定存储String类型
  mThreadLocal.set("main");//在主线程中存储"main"字符串

        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                mThreadLocal.set("t1");//在t1线程中存储"t1"字符串
                Log.i(TAG, "t1-run:"+mThreadLocal.get());
            }
        });

        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {在t2线程中不存储任何值,直接获取
                Log.i(TAG, "t2-run:"+mThreadLocal.get());
            }
        });
        t1.start();
        t2.start();
        Log.i(TAG, "main-run:"+mThreadLocal.get());

最后日志输出的结果为:

com.sendi.threadlocaltest I/MainActivity: main-run:main
com.sendi.threadlocaltest I/MainActivity: t2-run:null
com.sendi.threadlocaltest I/MainActivity: t1-run:t1

根据输出的结果,很明显看出不同线程只能获取到各自存储的值,没有存储值则获取到null。它有点类似于HashMap,但实际内部实现却不同。
以下是它内部实现原理的解析。
在看源码之前,先上一张关系图
这里写图片描述

实现原理

  • 首先从它是set方法入手
    public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//根据当前线程去获取到一个ThreadLocalMap对象
        if (map != null)
            map.set(this, value);//将value设置进ThreadLocalMap中
        else
            createMap(t, value);//创建一个ThreadLocalMap对象,并将value存储到里边去
    }

createMap(Thread, T)方法源码如下:它创建一个ThreadLocalMap对象,并将该对象赋值给当前线程的threadLocals成员

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

getMap(Thread)方法比较简单,就是根据当前线程去获取到对应的ThreadLocalMap对象。

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

接下来看看ThreadLocalMap的set方法。

由于关注的是ThreadLocal的对数据的存储和获取,所以这里我们只看 ThreadLocalMap的set和get方法:

  • ThreadLocalMap的set(ThreadLocal< ? > key, Object value)方法实现
private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            //通过ThreadLocal去获取到在数组中的索引
            int i = key.threadLocalHashCode & (len-1);
            //遍历Entry数组
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //如果遍历到的Entry对象中的引用ThreadLocal与传进来的key相同时,
                //则将传进来的value赋值给Entry中的value
                if (k == key) {
                    e.value = value;
                    return;
                }
                //如果遍历到的Entry对象中的引用ThreadLocal为null时,
                //则调用replaceStaleEntry()方法,此方法实现的大概思路在下边说明
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //如果以上两种情况都没有发生,则ThreadLocal和value封装成Entry并添加进Entry数组中
            tab[i] = new Entry(key, value);
            int sz = ++size;//长度加1
            //判断是否需要扩容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
  • replaceStaleEntry方法,里边大概逻辑是:
    接着i索引往下遍历找到一个存储的key与传进来的key相同的Entry对象,然后替换传进来的key和value,否则把传进来的ThreadLocal和value封装成Entry并添加进Entry数组中,最后决定要清除哪一些旧的Entry。

小总结

  1. ThreadLocal的set方法里边的逻辑就是根据所在线程获取一个ThreadLocalMap对象,
  2. 如果对象不为null,则调用ThreadLocalMap的set方法,
  3. 否则创建一个ThreadLocalMap对象并赋值给线程的threadLocals属性。
  4. 在ThreadLocalMap的set方法中,遍历Entry数组,如果遍历到的Entry对象中的ThreadLocal对象与set方法传进来的key相同时,则直接将传进来的value替换Entry中的value值
  5. 如果Entry对象中的ThreadLocal对象为null,则继续从i向后遍历Entry数组,如果set方法传进来的key存在于后续的Entry对象(后面用A对象表示)中,则将A对象的value换为set方法传进来的value,然后将Entry[i]与A对象互换。

到这里可以知道值其实不是存储在ThreadLocal中,而是存储在ThreadLocalMap的Entry数组中的某个Entry中。实际上ThreadLocal只是作为Entry中的key。

  • 接下来是ThreadLocal的get方法
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//根据所在线程获取ThreadLocalMap 对象
        if (map != null) {
        //根据ThreadLocal获取对应的Entry对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //获取Entry对象中的Value
                T result = (T)e.value;
                return result;
            }
        }
        //setInitialValue方法里边的逻辑跟set方法差不多,
        //只是将Value初始化为null在存储进去;然后返回一个null
        return setInitialValue();
    }
  • ThreadLocalMap 中的getEntry方法的实现
 private Entry getEntry(ThreadLocal<?> key) {
             //根据当前的ThreadLocal对象来获取对应的索引
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
            //当ThreadLocal对应的Entry对象不存在时调用此方法
            //此方法的逻辑在下边说明
                return getEntryAfterMiss(key, i, e);
        }
  • getEntryAfterMiss(key, i, e)的逻辑:继续向下寻找一个与传进来的ThreadLocal对象相同的key所在的Entry,同时对key为null的Entry进行清除。

小总结

  1. ThreadLocal的get方法中,通过所在线程去获取ThreadLocalMap对象
  2. 通过ThreadLocalMap对象获取Entry对象
  3. ThreadLocalMap在获取Entry对象的过程中,同时对存放ThreadLocal为null的Entry进行清除

最后是ThreadLocalMap中的结构:

ThreadLocalMap是ThreadLocal的一个静态内部类

  static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {

            Object value;//真正把值存放在此

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //默认Entry数组容量
        private static final int INITIAL_CAPACITY = 16;

        //Entry数组
        private Entry[] table;

        //当前元素长度
        private int size = 0;

       //闸值,当当前元素长度大于等于此值时,会进行扩容
       //它等于 容量*2/3
        private int threshold; 
}

总结

  • 不同线程中存放着不同的ThreadLocalMap对象
  • ThreadLocal并没有存放值,它只是充当key的角色,根据它的hash值可计算出在Entry数组中的位置,实际上值是存放在它的一个内部类ThreadLocalMap中的Entry
  • 在每次的get和set方法中,都会对key为null的Entry进行清除
### Java `ThreadLocal` 工作原理 `ThreadLocal` 是一种特殊的变量容器,允许每个线程拥有该变量的一个独立副本。这意味着不同线程之间无法相互干扰彼此持有的 `ThreadLocal` 变量实例[^1]。 具体来说,在 JVM 中每一个线程都维护着一个名为 `ThreadLocalMap` 的哈希表结构来保存这些特定于当前线程的数据项。每当调用 `set()` 方法向某个 `ThreadLocal` 对象赋值时,实际上是在对应的线程内部创建了一个键值对记录;当通过 `get()` 获取值的时候,则是从这个映射关系里查找并返回相应的对象引用[^2]。 对于继承自父级线程属性的情况(即 `InheritableThreadLocal`),JVM 还会复制一份来自父进程的相关配置给新启动的孩子们使用[^3]。 ```java public class ThreadLocalExample { private static final ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0); public void increment() { Integer value = threadLocalValue.get(); threadLocalValue.set(value + 1); } public int getValue() { return threadLocalValue.get(); } } ``` 这段代码展示了如何定义一个初始值为零 (`withInitial`) 的整数类型的 `ThreadLocal` 实例,并实现了两个方法用于操作其上的数值——增加计数器(`increment`) 读取当前值(`getValue`)。 ### 应用场景 #### 数据库连接池管理 在一个典型的Web应用服务器环境中,多个请求可能并发执行数据库查询任务。为了提高性能资源利用率,通常采用连接池技术预先分配一定数量的数据库链接供后续重用。此时就可以利用 `ThreadLocal` 来确保每次HTTP请求处理过程中所使用的都是同一个持久化的Session对象,从而简化事务管理错误恢复逻辑[^4]。 #### 用户上下文传递 假设存在一个多租户SaaS平台架构下的微服务组件通信链路,其中涉及到跨层传播认证信息的需求。借助 `ThreadLocal` ,可以在进入业务流程之初就将登录用户的唯一标识符存入内存空间内,之后无论经过多少次函数调用跳转都不会丢失这条重要线索,直到整个交易结束为止自动清除掉临时缓存的内容。 #### 并发计算中的状态保持 考虑这样一个例子:有一个复杂的算法需要分成若干个小部分由不同的工作单元协同完成。如果希望各个子任务能够共享某些中间结果而不必担心同步竞争条件带来的麻烦的话,那么完全可以把它们封装成静态字段形式并通过 `ThreadLocal` 方式来进行分发控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值