1.目的
ThreadLocal目的是保存一些线程级别的全局变量,比如connection,或者事务上下文,避免这些值需要一直通过函数参数的方式一路传递。
2. 常见用法
举例其中一种常见用法:
public class Test2 {
public static void main(String[] args) throws InterruptedException {
testThreadLocal();
}
private static void testThreadLocal() {
Util.setGlobalName("zili.dengzl");
new Foo().printName();
}
}
class Foo{
public void printName(){
System.out.println("globalName="+Util.getGlobalName());
}
}
class Util {
private static final ThreadLocal<String> globalName = new ThreadLocal<String>();
public static String getGlobalName() {
return globalName.get();
}
public static void setGlobalName(String name) {
globalName.set(name);
}
}
3.实现分析
要实现上面这样的功能,最简单的想法是用一个Map<Thread,T>,如下:
class MockThreadLocal<T> {
private Map<Thread, T> map = new HashMap<Thread, T>();
public T get() {
return (T) map.get(Thread.currentThread());
}
public void set(T value) {
map.put(Thread.currentThread(), value);
}
}
这样也能实现ThreadLocal的效果,但是有一个问题,当对应的线程消失后,map中对应的线程值并不会被回收,从而造成内存泄露。
事实上ThreadLocal是这样做的:
每个Thread都有一个threadLocalMap,key是threadLocal对象,value是具体使用的值。ThreadLocal对象的get就是先取得当前的Thread,然后从这个Thread的threadLcoalMap中取出值。set类似。
下面看下具体代码:
/**
* 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);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
注意这里如果取到没有该线程对应的值,会调用setInitialValue();,最终调用initialValue()生成一个值,这也是我们很多场景下要override这个方法的原因;
下面看一下getMap(Thread t)方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
在Thread类中:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
由此可见,所有的ThreadLocal的信息,最终是关联到Thread上的,线程消失后,对应的Thread对象也被回收,这时对应的ThreadLocal对象(该线程部分)也会被回收。
这里为什么是一个ThreadLocalMap呢,因为一个线程可以有多个ThreadLocal变量,通过map.getEntry(this)取得对应的某个具体的变量。
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
最后要注意的一点是,ThreadLocalMap的Entry是一个weakReference:
/**
* 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;
}
}
这里主要因为ThreadLocalMap的key是ThreadLocal对象,如果某个ThreadLocal对象所有的强引用没有了,会利用weakref的功能把他回收掉,然后复用这个entry。
考虑一下如果不用weakReference会出现什么情况:假设某个对象是这样引用的
private final ThreadLocal<String> globalName = new ThreadLocal<String>();
注意没有static,然后这个对象被不断的new出来,然后死掉,每次ThreadLocalmap中都会多出一个entry,然后这个entry强引用一个ThreadLocal对象,ThreadLocalMap本身就没有办法确定哪个entry是不用了的,如果恰好这个线程是线程池中的,会存活很久,那就杯具了。
ThreadLocalMap用了weakReference,失去强引用的ThreadLocal对象会在下次gc时被回收,然后ThreadLocalMap本身在get和set的时候会考察key为空的Entry,并复用它或者清除,从而避免内存泄露。
这样看来,HashMap也有一样的问题,但为什么hashMap不这样呢,因为hashMap的put是业务代码操作的,因此如果有长期存活的HashMap,(比如static的)业务代码put进去就有义务去remove,但ThreadLocal的put操作时ThreadLocal类干的,业务代码不知道,因此也不会去做remove,而ThreadLocalMap本身不知道引用他的某个entry的key的对象什么时候死掉了,那么如果不用弱引用,就不知道这个ThreadLocal对象什么时候需要回收了。
附:
这里补充一下weakReference的用法供参考(当强引用不存在时,下次垃圾回收会回收弱引用所引用的对象):
Object o = new Object();
WeakReference<Object> ref = new WeakReference<Object>(o);
System.out.println(ref.get());
o=null;
System.gc();
System.out.println(ref.get());
结果输出:
java.lang.Object@de6ced
null
4. FAQ
4.1 为什么一般的ThreadLocal用法都要加static,如下:
class Test {
private static final ThreadLocal<String> globalName = new ThreadLocal<String>();
}
answer:事实上,不一定是要static,但使用它的对象在业务需要范围类一定要是单例。因为根据前面的分析,ThreadLocalMap是以ThreadLocal对象为key的,如果Test类不是static,也不是单例的,那么两个Test对象就有两个key,取出来的数据肯定不同
class TestThreadLocal{
public static void main(String[] args) {
Test t1 = new Test();
Test t2 = new Test();
t1.pool.set("a");
System.out.println(t1.pool.get());
System.out.println(t2.pool.get());
}
}
class Test{
public ThreadLocal pool = new ThreadLocal();
}
输出将会是:a,null
原因就无需多解释了。唯一需要啰嗦的一点是,就算一般情况都是单例,上面那个weakreference还是必要的,因为作为框架代码,不能保证正常使用的情况下一个线程有很多ThreadLocal,如果不用weakreference,就会有内存泄漏的风险,特别是针对线程池中的线程。