ThreadLocal 是一个线程的局部变量,只有当前线程可以访问,是线程安全的。在使用时,需要为每一个线程分配不同的对象时,需要在应用层面保证。即 ThreadLocal 只是起到了简单的容器作用。如果在应用上每个线程分配了相同的对象实例,那么 ThreadLocal 也不能保证线程安全。这一点需要注意
比如 :SimpleDateFormat 对象不是线程安全的,在使用的时候,就需要使用 ThreadLocal 为每一个线程都产生一个 SimpleDateFormat 对象实例
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);
}
在 set 时,首先获得当前线程对象,然后通过 getMap() 拿到线程的 ThreadLocalMap,并将值设入 ThreadLoclMap 中。而 ThreadLocalMap 可以理解为一个 Map,但是它是定义在 Thread 内部的成员(ThreadLocalMap 本身是 ThreadLocal 的一个内部类),在 Thread 类的第 182 行中可以看到:
ThreadLocal.ThreadLocalMap threadLocals = null;
而设置到 ThreadLocal 中的数据,也正是写入到了Thread类中 threadLocals 这个 Map。其中,key 为 ThreadLocal 当前对象,value 就是我们需要的值。而 threadLocals 本身就保存了当前自己所在线程的所有 “局部变量”,也就是一个 ThreadLocal 变量的集合。
在进行 get() 操作时,自然就是将这个 Map 中的数据拿出来:
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();
}
首先,get() 方法也是先取得当前线程的 ThreadLocalMap 对象。然后,通过将自己,即 ThreadLocal 本身作为 key 取得内部的实际数据。
在了解了 ThreadLocal 的内部实现后,我们自然会引出一个问题。那就是这些变量是维护在 Thread 类内部的(ThreadLocalMap 定义所在类),这也意味着只要线程不退出,对象的引用将一直存在。
当线程退出时,Thread 类会进行一些清理工作,其中就包括 ThreadLocalMap 。
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
因此,如果我们使用线程池,那就意味着当前线程未必会退出(比如固定大小的线程池,线程总是存在)。如果这样,将一些大对象设置到 ThreadLocal 中(它实际保存在线程持有的 threadLocals 中),可能会使系统出现内存泄露的可能。
此时,如果你希望及时回收对象,最好使用 ThreadLcoal.remove() 方法将这个变量移除。就像我们习惯性地关闭数据库连接一样。
另外,如果对 ThreadLocal 的变量手动设置为 null,比如 tl = null ,那么这个 ThreadLocal 对应的所有线程的局部变量都有可能被回收。要了解这里的回收机制,我们需要更进一步了解 ThreadLocalMap 的实现。
ThreadLocalMap 的实现使用了弱引用。弱引用是比强引用弱得多的引用。Java 虚拟机在垃圾回收时,如果发现弱引用,就会立即回收。ThreadLocalMap 内部由一系列 Entry 构成,每一个 Entry 都是 WeakReference :
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
这里的参数 k 就是 Map 的 key ,v 就是 Map 的 value。其中 k 也就是 ThreadLocal 的实例。作为弱引用使用( super(k)就是调用了 WeakReference 的构造函数)。因此,虽然这里使用 ThreadLocal 作为 Map 的 key,但是实际上,它并不真的持有 ThreadLocal 的引用。而当 ThreadLocal 外部强引用被回收时,ThreadLocalMap 中的 key 就会变成 null 。当系统进行 ThreadLocalMap 清理时,就会自然将这些垃圾数据回收。