ThreadLocal源码简单分析

本文深入解析了ThreadLocal的实现原理,包括构造方法、set、get等核心方法的源码分析,并探讨了ThreadLocalMap的内部结构及工作方式。此外,还解答了关于ThreadLocal的常见疑问。

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

ThreadLocal是什么。百度上说,ThreadLocal并不是一个Thread,而是Thread的局部变量。
本文会先从三个方法开始源码解析。构造方法、set、get。最后会有一个总结和问答环节(自问)。
以下为jdk1.8源码

构造方法

构造方法是空的。什么都没做

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

set方法

1 获取当前线程
2 调用getMap(t) ,从当前线程里获取ThreadLocalMap
3.1 如果map不为null,set值,key为这个ThreadLocal对象, 好多博客里写是这个线程,这是错误的。
3.2 如果map为null,创建map

public void set(T value) {
    // 1 获取当前线程
    Thread t = Thread.currentThread();
    // 2 从当前线程里获取ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // 3.1 如果map不为null,set值
    if (map != null)
        map.set(this, value);
    // 3.2 如果map为null,创建map
    else
        createMap(t, value);
}
2 getMap(t)方法。

从Thread的成员变量中获取ThreadLocalMap。

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

ThreadLocalMap大致与hashMap差不多。以下为几个不同点。
1 hashmap底层为数组+链表,ThreadLocalMap底层为数组。
2 底层Entry继承了WeakReference弱引用,key是一个弱引用
3 获取角标时,使用了AtomicInterger.getAndAdd(0x61c88647)获取hash值。1640531527十进制,据说这个值可以更好的散列。
4 默认Entry长度为16,默认负载因子为2/3。

static class ThreadLocalMap {
    // 1 底层Entry继承了WeakReference弱引用,key是一个弱引用。 
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

   ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
       table = new Entry[INITIAL_CAPACITY];
       // 2 获取角标时,使用了AtomicInterger.getAndAdd(0x61c88647)获取hash值。
       int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
       table[i] = new Entry(firstKey, firstValue);
       size = 1;
       // 3 默认Entry长度为16,默认负载因子为2/3。
       setThreshold(INITIAL_CAPACITY);
     }


     private void setThreshold(int len) {
         threshold = len * 2 / 3;
     }

}
3.2 createMap方法

底层是创建了ThreadLocalMap对象。把map对象放在线程成员变量上。

new ThreadLocalMap大致做以下几件事。
1 初始化一个16长度的Entry数组对象。
2 根据原子类Integer获取hash地址。从而获取在数组中的角标。
3 新建entry赋值。
4 设置扩容长度,16 * 2 / 3 = 10;

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

get方法

1 获取当前线程,从当前线程中获取threadLocalMap
2.1 map不是null,根据key this来获取entry。
2.2 map是null,setInitialValue()

public T get() {
    // 1  获取当前线程,从当前线程中获取threadLocalMap
    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();
}
setInitialValue

1 initialValue,获取默认初始值
2 从当前线程中获取map
3.1 map不为null,set值,key为this这个threadLocal对象
3.2 map为null,调用createMap方法,初始化map

private T setInitialValue() {
    // 1 initialValue,获取默认初始值
    T value = initialValue();
    // 2 从当前线程中获取map
    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;
}

总结:threadLocal的底层实现为threadLocalMap,threadLocalMap大致与hashMap差不多,底层Entry继承了弱引用,弱引用在下一次垃圾回收的时候会回收 ,角标是通过原子类获取hash值。threadLocalMap的key为这个threadLocal对象。这个threadLocalMap放在Thread的成员变量里。thread和threadLocal和threadLocalMap的关系如下:

这里写图片描述


疑问

问题:为何底层使用map。毕竟只能存一个key,只能存一个val obj。为什么不直接把val放在线程的成员变量上储存?
答:可以使用多个ThreadLocal对象,也就是多个key。不是一个key。

问题:threadLocalMap使用WeakReference弱引用,在下次gc时候一定会被回收,如果正在使用中,垃圾回收怎么办?
这里写图片描述
图来自博客:http://blog.youkuaiyun.com/qq_27258799/article/details/51968527

答:回答这个问题,要先理清楚引用之间的关系,哪些是强引用,哪些是弱引用。虽然key是弱引用,但是在使用中肯定外面还有强引用在threadLocal。所以不可能被回收。

问题:threadLocal内存泄露问题。
当threadLocal的外部强引用释放掉,map的key因为是弱引用,所以threadLocal被gc,map里的key为null,如果当前线程迟迟不死亡,岂不是这个map的null key的value永远没有办法回收。
答:threadLocal在get和set的时候有对nullkey的特殊处理。会把value变为null,Entry变为null。具体可以看一下上图的博客。

问题:为何要把threadLocalMap的key作为弱引用。
答:1如果threadLocalMap的引用为强引用的话,如果释放了threadLocal的外部强引用,那么这个threadLocal将会在线程销毁之前一直存在。2如果threadLocalMap的引用为弱引用的话,如果释放了threadLocal的外部强引用,那么这个threadLocal将会在下一次gc回收。第2种对于内存来说是利用最大化。

问题:threadLocal是什么
threadLocal是在Thread里的成员变量ThreadLocalMap的key。

问题:因为threadLocalMap存在thread中,所以如果使用了线程池,再使用threadLocal是否会取到垃圾数据?
是的,如果是这种情况会出现问题,所以在使用threadLocal使用前或者结束后需要调用remove方法销毁之前的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值