2016-04-03
ThreadLocal
ThreaLocalMap
ThreadLocal reinitialized in threadpool
基本定义及解读
- 首先,每个线程都有变量的一个本地独立副本,保证线程之间的数据不会互相影响
- 可以通过重写initialValue()方法实现ThreadLocal的默认初始值
protected static final ThreadLocal<String> TL_EXAMPLE =
new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "default";
}
}; - 为什么说明中建议定义为静态static方法呢?不了解ThreadLocal原理的同学可能就糊涂了,既然是需要满足多线程并发的,怎么会定义为一个静态的类成员变量呢?
只要大家看一下ThreadLocal的源码就了解了,它有个静态内部类叫ThreadLocalMap<ThreadLocal, Object>, 此Map在Thread类中被定义为了一个类成员变量,即每个Thread线程中都有一个独立ThreadLocalMap副本,它的值只能被当前线程读取和修改
想像一下某个类中定义了多个ThreadLocal<?>变量,在当前线程中通过ThreadLocalMap.get(ThreadLocal)获取到相应的变量副本。
所以ThreadLocal变量本身不是副本,你可以把他当成一个代理,而ThreadLocalMap中存放了线程内的一个一个线程副本,ThreadLocal只是ThreadLocalMap内弱引用的Key(在ThreadLocal对象失效时可以及时的清理ThreadLocalMap)。
这也回答了为什么ThreadLocal可以定义为static, 它只是Map中的Key而已,不同线程的Map副本获取同一个Key的值完全不会冲突。
类ThreadLocal:
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();
}
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {...}
类Thread:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Thread1 :
ThreadLocalMap1 :
<ThreadLocal1, String>
<ThreadLocal2, Integer>
Thread2 :
ThreadLocalMap2 :
<ThreadLocal1, String>
<ThreadLocal2, Integer>
- 继续延伸出一个问题: ThreadLocal类本身是线程安全的么?
通过源码看到不管是get,set还是createMap都没有做任何的同步或者并发锁。答案是安全的,因为实现都是基于当前线程的。
线程池与ThreadLocal变量的初始化
在线程池复用的情况下,若ThreaLocal数据没有被清理掉,会被后面的请求复用然后拿到被你修改过的值!
之前在实现日志上下文LogContext的时候碰到了类似问题:
- 请求A进入Controller中, 开启线程A,LogContext中记录了大量的ThreadLocal中间变量值,在请求响应结束后,请求线程A回归线程池;
- 请求B进入Controller中,复用线程A,LogContext会在之前变量值的基础上继续添加信息,这样的日志信息成了叠加的了。
不管是基于自己实现的线程池,还是应用服务器(如Tomcat)的线程池,都需要小心这一点!
标准或者规范做法是在线程变量使用完毕之后,或者finally代码块中调用 threadLocalVariable.remove() 移除,以防被其他线程复用。