ThreadLocal源码简单分析

来源:互联网 发布:dao类中sql语句怎么写 编辑:程序博客网 时间:2024/06/06 11:45

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.csdn.net/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方法销毁之前的数据。