ThreadLocal中的WeakReference
Posted 六月风花雪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal中的WeakReference相关的知识,希望对你有一定的参考价值。
在一般的网站开发中,基于Java的Web 框架都使用了ThreadLocal来存储一些全局的参数,在拦截器\\Filter中设置变量,让变量可以在任意地方被获取。
一早就了解到里面有用到WeakReference(弱引用),但对弱引用仅限于一种懵懂的概念,并且认为只要GC,弱引用的对象就被回收掉了,实际情况呢?
Thread对象有一个变量名为 threadLocals 的 ThreadLocalMap对象,这个类和HashMap类似,里面定义了一个Entry数组,不过这个Entry对象弱引用了ThreadLocal
/** * 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); //调用父类构造函数,设置Reference的referent属性 value = v; } } /** * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */ private void set(ThreadLocal key, Object value) { // We don\'t use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal k = e.get(); 获取Reference的referent属性 if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); //清理掉陈旧Entry,这种Entry是由于ThreadLocal变量对象被回收后k==null造成的 return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
注意以下方法
if (k == null) { replaceStaleEntry(key, value, i); //清理掉陈旧Entry,这种Entry是由于ThreadLocal变量对象被回收后k==null造成的 return; }
这个地方其实也解释了通常所说的ThreadLocal变量可能导致内存溢出的问题:
Entry弱引用了ThreadLocal,因此Entry是否存活,不会影响ThreadLocal的生命周期,ThreadLocal在没有其他对象引用后被回收,但是它对应的Entry中的value实际还在被Entry引用,而这不是一个弱引用,如果不清掉掉Entry,value就被一直强引用,无法释放,那么就可能会内存溢出
如果一直没有ThreadLocal变量访问,并且线程一直存活,就不会清理陈旧Entry,value永远无法释放,这也是某种意义上的内存泄露
一直以来,我把WeakReference理解成:弱引用的对象一定会在下次GC时回收掉,按此推断,ThreadLocal变量是不太安全的,因为用着用着就可能被GC掉了。
但是如果ThreadLocal有这么严重的问题,谁会去用呢!
实际上对象B弱引用了A,如果A除了B以外,没有其他引用(强、软引用)时,才会把A GC调,这也是为什么ThreadLocal实际上是一个安全的操作。
Thread.threadLocals 是一个Map,每个Entry都弱引用了ThreadLocal对象
因此Thread.threadLocals对每个ThreadLocal对象都是弱引用关系
调用ThreadLocal.get()方法
/** * Returns the value in the current thread\'s copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread\'s value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); 可以看到get实际上是获取当前线程的ThreadLocalMap if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); } /** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
可以理解为一个ThreadLocal实例,在多个线程都存在副本,并且在不同线程设置的值,都不会影响到其他线程,因为ThreadLocal实例是存储在当前Thread对象上的
也就是说,如果Thread死掉后,它和ThreadLocal对象也就没有任何关系了,这对我们的程序没有任何影响,因为我们也没有机会再取它在Thread中的副本了;
而ThreadLocal变量被设置为null后,虽然Thread中的Entry弱引用了ThreadLocal,即使Thread对象还在,当ThreadLocal对象没有被其他对象引用后,它就可以被GC调了,Thread对象产生了弱引用,不会影响到ThreadLocal变量的回收
其他知识点:
需要注意的是,当一个线程执行Thread.start()后,如果start()方法已经执行完毕了,虽然Thread对象还在,其实它的生命周期已经结束了,对应的ThreadLocal变量可以被回收
如果线程再次调用Thread.start()方法,会抛出异常,因此不用关注线程再次被启动,因为它已经无法再被启动了(PS:线程复用技术并非是重新start,而是在线程内执行多个任务)
以下情况ThreadLocal变量是永远不会回收的
public class FlagContext { /** * ThreadLocal变量 */ private final static ThreadLocal<Boolean> tbFlag = new ThreadLocal<Boolean>(); }
FlagContext的静态变量引用了ThreadLocal实例tbFlag,tbFlag是个final 声明的变量,永远不会为空,因此,这种情况下,tbFlag对应的值,只会在线程生命周期结束,或调用tbFlag.remove()才会被回收!原因呢:
ThreadLocal永远被FlagContext强引用了,而Thread和ThreadLocal不管是否是弱引用,即使Thread中的Entry被回收了,也不会被GC掉!
以上是关于ThreadLocal中的WeakReference的主要内容,如果未能解决你的问题,请参考以下文章