Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量。
在我们日常 Web 开发中难免会遇到需要把一个参数层层的传递到最内层的情况,但是中间层可能根本不需要使用这个参数,因此这样我们完全没有必要在每一个方法里面都传递这样一个通用的参数。Java的Web项目大部分都是基于Tomcat,每次访问都是一个新的线程,这样让我们联想到了,每一个线程都独享一个ThreadLocal,在接收请求的时候set特定内容,在需要的时候get这个值。例如,登录信息就可以这么存储。
ThreadLocal<Map> userInfo = new ThreadLocal<Map>();
但是在获取userInfo的过程中,却发现它时有时无,甚至会有其他用户的登录信息。
时有时无,是因为页面首次跳转到这个页面时,因为cookie中并无登录信息,拦截器也就获取不到登录信息,因此赋值为空。
有其他用户的登录信息,则是因为对登录信息赋值时,会先判断一下,对象是否有值,如果有值,则并不覆盖原值,或者是因为并未走对此赋值的拦截器,总之产生了内存泄漏的问题。
每次的请求都会有一个Thread,每个 Thread都会拥有一个 ThreadLocalMap变量,来存放属于该 Thread的所有 ThreadLocal变量。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Map中的key为一个threadlocal实例.
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
}
这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收。
只要这个线程对象被gc回收,就不会出现内存泄露,但在使用线程池的时候,请求结束后线程并不会被销毁的,会再次复用因此就出现了内存泄露。因此建议在ThreadLocal业务周期处理完成时,最好显示的调用remove()方法,清空“线程局部变量”中的值,或者在业务周期开始时,重新赋值。