Skip to content

ThreadLoacal的实现原理

ZhangPan edited this page Jul 13, 2025 · 10 revisions

ThreadLocal 概述

每个线程都持有一个 ThreadLocalMap, ThreadLocalMap 是一个位于 ThreadLocal 中的静态内部类。与 HashMap 类似,ThreadLocalMap 也是基于哈希表实现,与 HashMap 不同的是,ThreadLocalMap 是使用线性探测再散列法处理的哈希冲突使。ThreadLocalMap 有一个 Entry 数组,Entry 继承自 WeakReference<ThreadLocal<?>>,内部有一个 Object 的成员变量 value。也就是说它的 key 是 ThreadLocal,value是我们通过 ThreadLocal 的 set 设置的值。

ThreadLocal 的 set 方法会首先获取到当前线程,并从线程中获取到 ThreadLocalMap,然后通过 ThreadLocalMap 的 set 方法将 ThreadLocal 与值设置到 ThreadLocalMap 中。ThreadLocalMap 的 set 方法就是一个插入到哈希表的过程。set 方法首先获取到 ThreadLocal 的哈希值,通过一个哈希函数获取到其在 ThreadLocalMap 中的存储位置,如果没有哈希冲突则直接将这个 Entry 放入该位置,如果出现哈希冲突,则通过线性探测再散列法探测空闲的位置进行插入操作。同时还会涉及到扩容问题,这里不再赘述。

ThreadLocal 的 get 方法同样会先获取到当前线程,然后从线程中取出 ThreadLocalMap 并调用它的 getEntry(ThreadLocal<?> key), getEntry 方法则是从哈希表中查找值的过程,通过同样的哈希运算找到 Entry 的存储位置,如果当前位置刚好是要查找的 Entry 则直接返回,如果不是要查找的 Entry,则说明出现了哈希冲突。使用线性探测法查找 Entry 的存储位置,直到找到为止。

可以看到,ThreadLocal 的 set 与 get 方法都是获取到当前的线程,然后从当前线程中的 ThreadLocalMap 进行插入或查找数据的。因此,如果调用 set 与 调用 get 方法不是同一个线程,那 get 方法肯定查找不到 set 的数据。由此,ThreadLocal 实现了线程级别的数据存储功能。

ThreadLoacal 使用及源码分析

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,且存储的数据只有在该线程中才能获取的到,其他线程无法获取。

1.ThreadLocal的基本使用:

    public static void main(String[] args) throws InterruptedException {

        ThreadLocal<Boolean> threadLocal=new ThreadLocal<>();

        new Thread(){
            @Override
            public void run() {
                super.run();
                threadLocal.set(true);
                System.out.println("在子线程中获得threadLocal中的值:"+threadLocal.get());
            }
        }.start();
        Thread.sleep(100);

        System.out.println("在主线程中获得threadLocal中的值:"+threadLocal.get());

    }

输出结果:

 在子线程中获得threadLocal中的值:true
 在主线程中获得threadLocal中的值:null

ThreadLocal的源码分析

ThreadLocal是一个泛型类。它的重点是有一个ThreadLocalMap的静态内部类和两个核心方法,get和set。

2.ThreadLocal的 set 方法分析

set方法的源码如下:

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取线程中的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 将值存储到ThreadLocalMap中
        map.set(this, value);
    } else {
        // 创建ThreadLocalMap,并存储值
        createMap(t, value);
    }
    
void createMap(Thread t, T firstValue) {
    // 实例化当前线程中的ThreadLocalMap
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

上述代码首先获取到了当前线程,然后从当前线程中获取 ThreadLocalMap,ThreadLocalMap 是一个存储K-V的集合,我们后边分析。如果此时ThreadLocalMap 不为空,那么就通过 ThreadLocalMap 的 set 方法将值存储到当前线程对应的 ThreadLocalMap 中。如果 ThreadLocalMap 为空,那么就创建 ThreadLcoalMap,然后将值存储到 ThreadLocalMap 中。并且,这里我们注意到 ThreadLocalMap 的 key 是当前的 ThreadLocal。

3.ThreadLocal 的 get 方法

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

get方法的代码如下:

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程对应的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 从ThreadLocalMap中取出值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果值为空则返回初始值
    return setInitialValue();
}
// 为ThreadLocal设置初始值
private T setInitialValue() {
    // 初始值为null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        // 创建ThreadLocalMap
        createMap(t, value);
    }
    if (this instanceof TerminatingThreadLocal) {
        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
    }
    return value;
}
// 初始值为空
protected T initialValue() {
    return null;
}

get方法依然是先获取到当前线程,然后拿到当前线程的ThreadLocalMap,并通过ThreadLocalMap的getEntry方法将这个ThreadLocal作为key来取值。如果ThreadLocalMap为null,其实通过setInitialValue方法返回了一个null值。

总的来看,set方法将value放到了当前线程的ThreadLocalMap中,而key是当前的这个ThreadLocal。而get方法则是获取当前线程中的ThreadLcoalMap,然后将这个ThreadLocal作为key来取出value。到这里其实我们已经能够解答为什么ThreadLocal中的值只能被设置这个值的线程可见了。但是似乎有点只见树木不见森林的感觉,毕竟ThreadLocalMap是什么东西呢?

ThreadLocalMap

ThreadLocalMap 是一个存储(key,value)键值对的类,它的实现与 HashMap 有些类似。它比较特别的地方是它内部的节点Entry:


static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

Entry是位于ThreadLocalMap中的静态内部类,表示Map的节点(key,value),并且它继承了WeakReference,WeakReference泛型为ThreadLocal。看Entry的构造方法有两个参数ThreadLocal与一个Object,分别表示key和value。这里注意,在构造方法中将ThreadLocal作为参数调用了父类的构造方法,也就是ThreadLocalMap的key是一个弱引用,而value是一个强引用。意味着,当没有引用指向key时,key会在下一次垃圾回收时被JVM回收。

那 ThreadLocalMap 在哪里使用了呢?其实就在线程Thread类中:

class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

可以看到Thread类中维护了一个ThreadLocalMap的成员变量。

ThreadLocalMap 是一个存储K-V类型的数据结构,并且 Thread 类中维护了一个 ThreadLocalMap 的成员变量。代码如下:

public class Thread implements Runnable {

    ThreadLocal.ThreadLocalMap threadLocals = null;
    ...
}

ThreadLocalMap 是 ThreadLocal 的内部类,ThreadLocalMap 的类结构如下:

static class ThreadLocalMap {

    private Entry[] table;
    private int size = 0;
    
}

ThreadLocalMap 内部维护了一个 Entry 数组,和一个 int 类型的 size。Entry 是 ThreadLocalMap 的内部类,它就是对我们设置的 value 的封装,代码如下:

static class ThreadLocalMap {

    private Entry[] table;
    private int size = 0;
    
}

ThreadLocalMap 的 set 方法

private void set(ThreadLocal<?> key, Object value) {


    Entry[] tab = table;
    int len = tab.length;
    // 获取key哈希值,作为在Entry数组中的位置
    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;
        }
    }
    // 将key和value封装到Entry中,并放入Entry数组
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

通过ThreadLocalMap的set方法可以看出,ThreadLocalMap是一个哈希表结构。set方法是将value插入到哈希表中的操作。我们知道哈希表是会出现哈希冲突的,因此,上述代码首先使用线性探测再散列法进行哈希冲突的处理,然后再讲value封装成Entry,插入到Entry数组中。

ThreadLocalMap 的 getEntry 方法

private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; // 不存在哈希冲突的情况,取到了值 if (e != null && e.get() == key) return e; else // 存在哈希冲突的情况,则通过线性探测法来查找值 return getEntryAfterMiss(key, i, e); } 由于是从哈希表中取值,所有这个方法中一定存在两种情况,即存在哈希冲突和不存在哈希冲突。首先,如果不存在哈希冲突,那么直接从Entry数组中取出第i个元素即可。而如果存在哈希冲突,那么则需要继续线性探测来查找key的位置。getEntryAfterMiss就是线性探测的实现,无非就是循环遍历,这里就不再贴这个方法的代码了。

为什么要将 ThreadLocalMap 的 key 声明成弱引用呢?

这么做其实是为了有利于垃圾回收。想一下如果没有将ThreadLocal的key声明为弱引用会有什么问题?当使用完了ThreaLocal之后,解除了对它的引用,希望它能够被回收,但是此时JVM发现ThreadLocal无法被回收,因为在ThreadLocalMap中,ThreadLocal还作为key被引用,虽然ThreadLocal已经不再使用,但是仍然无法被回收。而如果将ThreadLocalMap的key声明为弱引用就不会存在无法回收ThreadLocal的问题。

了解了ThreadLocal的内部实现后,我们通过开篇的例题来分析以下ThreadLoca的存储过程:

  • (1)threadLoca 在子线程(称为threadA)中通过threadLocal的set方法将值设置为了true。在set方法中会首先获得threadA线程,然后拿到threadA中的ThreadLocalMap,并将threadLocal作为key,将true作为value存储到了ThreadLocalMap中;

  • (2)当在threadA线程中调用threadLocal的get方法时,get方法同样会先获取到threadA,然后拿到threadA中的ThreadLocalMap,接着以threadLocal作为key来获取ThreadLocalMap中对应的值,此时的ThreadLocalMap与第(1)不中的ThreadLocalMap是同一个,且key都是threadLocal。因此,在threadLocal中拿到的值为true;

  • (3)在主线程中调用threadLocal的get操作,此时get方法会首先获取到当前线程为主线程,然后拿到了主线程中的ThreadLocalMap,接着以threadLocal作为key来获取主线程的ThreadLocalMap中对应的值,此时的ThreadLocalMap与(1)步中的ThreadLocalMap并非同一个。因此,主线程中获取到的值是null。

ThreadLocal的示意图如下:

Java并发系列番外篇:ThreadLocal原理其实很简单

公众号:玩转安卓Dev

Java基础

面向对象与Java基础知识

Java集合框架

JVM

多线程与并发

设计模式

Kotlin

Android

项目相关问题

Android基础知识

Android消息机制

Android Binder

View事件分发机制

Android屏幕刷新机制

View的绘制流程

Activity启动

Framework

性能优化

Jetpack&系统View

第三方框架实现原理

计算机网络

算法

Clone this wiki locally