ThreadLocal

简介: ThreadLocal对象是线程的局部变量,每个线程都能在其中保存只属于自己的内容。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

ThreadLocal对象是线程的局部变量,每个线程都能在其中保存只属于自己的内容。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

结构体系大概如下:
image

ThreadLocal如何保证这些变量只被当前线程所访问的呢?

ThreadLocal.set

首先看一下ThreadLocal的set():

public class ThreadLocal<T> {

    /* 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;
    
    /**
     * 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);
        else
            createMap(t, value);
    }
}

set时,首先获取当前线程对象,然后通过getMap获取线程的ThreadLocalMap,并将值设置ThreadLocalMap中,ThreadLocalMap可以理解为一个Map。map.set()如下:

    static class ThreadLocalMap {
        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            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) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
    }

方法中循环了map中的元素,对map中的key和入参key比较,如果相同则赋新值,如果map中key存在null值则替换,否则重新生成一个map元素。

如果没有map,则执行createMap():

    /**
     * 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);
    }

createMap中构造了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;
            setThreshold(INITIAL_CAPACITY);
        }

在这里,为Thread的threadLocals赋值。

ThreadLocal.get

而设置到ThreadLocal中的数据,实质上是写入了Thread的threadLocals属性。其中key为ThreadLocal当前对象,value就是我们需要的值。Thread的threadLocals保存了当前自己所在线程的所有“局部变量”,也就是一个ThreadLocal变量的集合。

在进行get操作时,自然就是将这个Map中的数据拿出来:

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

首先,get方法也是先取得当前线程的ThreadLocalMap对象,然后,将自己传入作为getMap的参数,而getMap返回的则是当前线程的threadLocals属性:

    /**
     * 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;
    }

即当前线程的threadLocals属性,也就是说,ThreadLocal类的getMap方法返回的是Thread类的threadLocals属性:

public class Thread implements Runnable {

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

因此,threadLocals是维护在Thread内部的,这意味着只要线程不退出,对象的引用就一直存在。

当线程退出时会进行一些清理工作,其中包括对ThreadLocalMap的清理:

    /**
     * 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;
    }

如果使用线程池的话,线程未必会退出,此时将一些比较大的对象放入ThreadLocal中(实际保存在线程持有的threadLocals Map中),可能会导致内存泄漏的问题。这时可以使用ThreadLocal.remove()进行移除:

    /**
     * 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)
             m.remove(this);
     }

实例演示

public class Test {

    static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    static ThreadLocal<String> anotherThreadLocal = new ThreadLocal<>();
    static AtomicInteger atomicInteger = new AtomicInteger(0);

    static class ThreadDemo implements Runnable {

        @Override
        public void run() {
            if (threadLocal.get() == null) {
                threadLocal.set("线程:" + atomicInteger.addAndGet(1));
            }
            if (anotherThreadLocal.get() == null) {
                anotherThreadLocal.set("anotherThreadLocal:" + atomicInteger.get());
            }
            System.out.println(threadLocal.get());
            System.out.println(anotherThreadLocal.get());
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ThreadDemo());
        }
    }
}

表明,一个ThreadLocal可以供多个线程共享,但是每个线程只能拿到属于自己的那一份存储。每一个线程的threadLocals的大小可以为多个,每一个ThreadLocal对象都可以会作为该map的key存在。

目录
相关文章
Maven之阿里云镜像仓库配置
方式一:全局配置可以添加阿里云的镜像到maven的setting.xml配置中,这样就不需要每次在pom中,添加镜像仓库的配置,在mirrors节点下面添加子节点: <id>nexus-aliyun</id> <mirrorOf>central</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.
|
Ubuntu
笔记本Ubuntu 设置合盖不自动休眠
经测试,适用于ubuntu 16.04 / 18.04 / 20.04
3370 0
|
存储
ThreadLocal你懂了,你还懂TransmittableThreadLocal嘛?
ThreadLocal你懂了,你还懂TransmittableThreadLocal嘛?
573 0
|
存储 算法 安全
深入理解 ThreadLocal
深入理解 ThreadLocal
深入理解 ThreadLocal
|
10天前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
1216 5
|
9天前
|
机器学习/深度学习 人工智能 前端开发
通义DeepResearch全面开源!同步分享可落地的高阶Agent构建方法论
通义研究团队开源发布通义 DeepResearch —— 首个在性能上可与 OpenAI DeepResearch 相媲美、并在多项权威基准测试中取得领先表现的全开源 Web Agent。
1180 87
|
10天前
|
云栖大会
阿里云云栖大会2025年9月24日开启,免费申请大会门票,速度领取~
2025云栖大会将于9月24-26日举行,官网免费预约畅享票,审核后短信通知,持证件入场
1773 12