ThreadLocal用来干什么的?
在ThreadLocal类中注释如下:为该类提供线程局部变量。这些变量不同于它们的普通对应物,每个访问它们的线程(通过其{@code get}或{@code set}方法)都有自己独立初始化的变量副本。{@code ThreadLocal}实例通常是类中的私有静态字段,这些类希望将状态与线程关联(例如,用户ID或事务ID)。
那么ThreadLocal的作用就是将变量封闭在该线程中,确保其是线程安全的。现在我们想给每一个线程分配一个特殊的连接。代码如下:
public class MyThread {
private static final ThreadLocal<String> threadId = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(()->{
threadId.set("connection_1");
System.out.println(threadId.get());
threadId.remove();
}).start();
new Thread(()->{
threadId.set("connection_2");
System.out.println(threadId.get());
threadId.remove();
}).start();
}
}
此时我们就可以在线程里安全的使用分配给线程的连接。
ThreadLocal原理
ThreadLocal之所以能将变量进行线程内封闭,主要是使用到了一个ThreadLocalMap的类。它是ThreadLocal中一个静态内部类,它就是一个Map结构,底层放的元素是使用ThreadLocal作为key,变量作为value的Entry。
Map的创建时机为第一次调用set()函数时,会判断getMap()是否为空,如果为空则会创建ThreadLocalMap并与线程绑定。
ThreadLocal内存泄漏
在使用ThreadLocal时,需要强调的一点是使用完毕后记得调用remove()方法进行手动的删除。否则会导致内存泄漏。那么为啥垃圾收集器收拾不掉这个小辣鸡呢?请看下面这张图
首先介绍一下弱引用:弱引用用来描述那些非必须对象, 只被弱引用关联的对象只能生存到下次垃圾收集发生。当垃圾收集器开始工作时,无论当前内存是否足够,都会回收掉它。下图是Entry类代码
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
那么假设我们此时已经使用完了ThreadLocal,那么ThreadLocal会被回收掉,但注意到Thread到Entry是存在着一条生命周期跟随线程的引用链的。那么value对象就会一直存在于内存中。
说明白了内存泄漏,那么为什么ThreadLocal要被设计为弱引用呢?很多文章说这是为了防止ThreadLocal的内存泄漏就止步于此。其实我们再看一下源码就可以发现,在get()方法时,如果ThreadLocal为null,那么肯定找不到对应的Entry,此时会一路调用到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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
initialValue()会直接返回一个null,那么此时我们会把Entry中对value的引用置为空。那么这个value对象的引用链就消失了。因此,我们可以把弱引用的设计看作是设计者对没有手动调用remove()方法进行删除的一种保护手段。