众所周知,ThreadLocal类是java提供线程本地变量的工具类。但父线程的本地变量却不能被子线程使用,代码如下:
1 public static void main(String[] args) {
2 ThreadLocal<String> threadLocal = new ThreadLocal<>();
3 threadLocal.set("abc");
4 System.out.println("父线程:"+threadLocal.get());
5 Thread t1 = new Thread(new Runnable() {
6 @Override
7 public void run() {
8 System.out.print("子线程:"+threadLocal.get());
9 }
10 });
11 t1.start();
12 }
运行结果如下:
至于原因呢,得先了解ThreadLocal存储的变量是怎么存储的。首先,让我们先看看Thread类的源码:在thread类中有声明这么一个成员变量——threadLocals
ThreadLocal.ThreadLocalMap threadLocals = null;
根据定义可以看出,这是ThreadLocal类里的静态内部类,它的结构是Map结构,以键值对的形式存储值。key值就是当前ThreadLocal类的实例,value值就是当前线程的本地变量。所以线程的本地变量是存在线程实例当中的,而不是存在ThreadLocal中。ThreadLocal只是一个工具类,体现在当ThreadLocal实例调用set()方法时,会将当前线程的threadLocals变量实例化。以下是ThreadLocal类的set()方法源码。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可以看到,先是获取当前线程的实例,再获取线程的成员变量threadLocals,如果threadLocals已经实例化,就直接以当前ThreadLocal类为key,存储的本地变量为value,存进threadLocals中。所以线程本地变量是存储在线程中的。如果threadLocals未实例化,则调用createMap()方法,该方法会调用ThreadLocalMap的构造方法,初始化一个以当前ThreadLocal类为key,存储的本地变量为value的Map。该Map是类似HashMap的,感兴趣的话可以继续往下看源码,此处不做扩展。
回到最开始的问题,子线程调用ThreadLocal类的get方法,源码如下:
public T get() {
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();
}
此时,会产生两个分支,一个是通过子线程的ThreadLocalMap的getEntry()方法获取Entry并返回value值,但由于子线程的ThreadLocalMap!=父线程的ThreadLocalMap,所以获取不到父线程的本地变量。另一个分支是返回setInitialValue()的返回值,setInitialValue()方法源码如下:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
可以看到value会默认为null,如果子线程的ThreadLocalMap已经实例化,则直接以ThreadLocal实例为key,null为value存进ThreadLocalMap中,否则就创建一个同上所述的ThreadLocalMap。
引发出的问题:ThreadLocal类是弱引用,一次GC后会为null,当key为null时,value值却还存在内存中,造成内存泄漏。所以ThreadLocal类最后一定要执行remove()方法。在GC之前将内存释放。
综上,第一次写博客,写得不好或者有错误的地方,请指正。