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