本文主要准备通过以下四个方面对ThreadLocal进行分析和说明
- 概要说明
- ThreadLocal使用
- 原理
- GC回收
1. 概要说明
举个不是很恰当的例子,如果把线程比作一个人,那么ThreadLocalMap就是他身上的所有口袋,每个口袋(ThreadLocal)可以放不同的工具(数据),无论这个人走到哪儿,他都可以使用自己口袋里面的工具
看一下代码
Thread.java内部里面持有一个map
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
可以看到这个ThreadLocalMap是ThreadLocal的一个内部类,注释上也表明这个map的维护工作是通过ThreadLocal这个类进行了。所以我们大概也可以猜测,通过ThreadLocal的调用,最后把一些变量放入到当前线程的ThreadLocalMap内部。
2. ThreadLocal的使用
先用一个简单的Main方法,只是new了一个ThreadLocal变量,这里main所在的主线程向threadLocal里面添加一个数字,看看发生了什么。
public class ThreadLocalTest {
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
public static void main(String[] args) {
threadLocal.set(1);
System.out.println(threadLocal.get());
}
}
threadLocal的set方法:
前两行,获取当前线程,还记得线程中有一个threadLocalMap吧,获取到这个map。
然后判断是否为空,空则创建一个ThreadLocalMap,不为空则set进去变量。
然后这个map的key是this,value就不用说了。this指的是当前ThreadLocal变量实例,所以就算有多个线程,对于每个线程来说,内部都有一个自己的map,key是ThreadLocal实例。那么如果有多个ThreadLocal变量呢?可以参考下图:
3. 原理
让我们看看ThreadLocal类的代码:
首先看到的是3个变量
threadLocalHashCode
nextHashCode
HASH_INCREMENT
这几个变量有什么用,我们可以猜测一下。首先看起来是和hashCode相关的,然后
private final int threadLocalHashCode = nextHashCode();
这段代码,意味着每次初始化一个ThreadLocal变量,都会通过nextHashCode()这个方法生成一个hashCode,这个hashCode是通过一个static的原子变量getAndAdd(HASH_INCREMENT)得到的
private static AtomicInteger nextHashCode = new AtomicInteger();
static意味着全局唯一,所以我们每new一个ThreadLocal对象,这个对象内部会持有一个唯一的hashCode,至于为什么增量是0x61c88647,看下注释,是为了在2的幂次方长度的hashtable得到更好的分布。
ThreadLocal核心方法:
setInitialValue() 为threadLocal变量设置初始值,防止get()空指针
get()、set(), remove(), 获取/设置/移除 threadLocalMap中的变量。
4. GC回收
正常情况下,由于变量最终是存在Thread类的ThreadLocalMap变量内的,所以线程执行结束后,随着线程对象的回收,threadLocalMap也会被正常回收掉,但是如果是线程池的模式,线程执行完成,没有被回收掉,threadLocalMap对象也不会被回收,可能会导致内存泄漏。
所以正确的打开方式是,使用完ThreadLocal变量,手动调用一下ThreadLocal.remove()方法进行清理。