ThreadLocal介绍
ThreadLocal是jdk版本引入的类:java.lang.ThreadLocal,使得每个线程可以独享变量副本,起到线程安全的作用。线程局部性的,ThreadLocal变量能在所属线程执行的各个方法中共享但是不需要在执行方法链中显示传递参数,也就是线程局部的执行方法链的全局变量。
ThreadLocal实现原理
首先说下,ThreadLocal、Thread、ThreadLocalMap、ThreadLocalMap.Entry、WeakReference这几个类之间的关系,这关系着ThreadLocal在jdk源代码层面的实现。
- ThreadLocal有个静态内部类:ThreadLocalMap
- ThreadLocalMap是个Map内部实现类似HashMap,但是其本身并没有实现java.util.Map接口;ThreadLocalMap的Entry类实现了WeakReference接口,WeakReference里放的是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;
}
}
同时
- Thread内,有个变量,threadLocals,是个ThreadLocalMap 类型的变量,里面可以存储多个线程局部变量,key是ThreadLocal类型,value是Object类型,见代码片段如下:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
下面说下,用户程序是怎么把变量放到执行当前代码的线程局部变量hash表:threadLocals里去的,例如要将一个int变量放到当前线程的局部变量hash表中,代码如下:
public class ThreadLocalTest {
public static void main(String[] args) {
//1.创建ThreadLocal变量
ThreadLocal tl = new ThreadLocal();
//2.将1存入线程局部变量hash表中
tl.set(1);
//3.取出先前放入的局部变量
System.out.println(tl.get());
}
}
上面第二步jdk源代码片段如下:
public void set(T value) {
//取得当前线程引用
Thread t = Thread.currentThread();
//找到当前线程的threadLocals变量,是ThreadLocalMap类型的
ThreadLocalMap map = getMap(t);
//当前线程的threadLocals变量已经初始化,直接将当前ThreadLocal作为key,放入threadLocals中
if (map != null)
map.set(this, value);
else
//当前线程的threadLocals变量未被初始化,则先初始化,并把当前值放入threadLocals,key为当前ThreadLocal
createMap(t, value);
}
上面第三步,jdk源代码如下:
public T get() {
//取得当前线程引用
Thread t = Thread.currentThread();
//找到当前线程的threadLocals变量,是ThreadLocalMap类型的
ThreadLocalMap map = getMap(t);
//当前线程的threadLocals变量已经初始化
if (map != null) {
//用当前ThreadLocal实例,取出entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//取出entry中的value
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
//若ThreadLocal实例,还没放入ThreadLocalMap ,调用get,给其初始一个空值作为value放入当前线程当前线程的threadLocals变量中
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
ThreadLocal使用场景
当变量业务客观上属于线程私有,但是线程执行方法链需要共享该变量,并不想通过方法参入传递该变量时,能够避免加锁操作,做到线程安全,且提高了程序的性能。例如:
- spring中的事务处理
- springmvc中,请求的参数封装到ThreadLocal中,在controller后的所有方法中随时可以获取,当然只限于当前线程执行的方法
使用ThreadLocal需要注意的问题
- 在使用线程池的程序中慎用,因为线程可以复用,执行不同的任务,线程局部变量一直共享,可能会串,出现意料之外的情况。如果,线程复用,但是变量随不同的任务,而不同,每次执行任务开始时,放入期望变量,任务结束时,移除变量。
- 程序中发生异步处理时,异步前后线程局部变量并不能自然传递,若需要传递,需要人为处理,不过这样会使得程序变得复杂,出现问题不好排查
- ThreadLocal中的变量会发生内存泄露,原因是,ThreadLocal变量最终都会ThreadLocalMap.Entry中,放入ThreadLocalMap中,ThreadLocalMap.Entry继承WeakReference,但是构造方法只把ThreadLocal放入到了WeakReference,value在ThreadLocalMap.Entry中,当jvm发生gc时,ThreadLocalMap.Entry的key即ThreadLocal实例会被回收,但是value却由于ThreadLocalMap.Entry属性强引用,不能被回收,线程一般一直存在,ThreadLocalMap中entry也一直存在,导致value不能被读取,却不能被回收,造成相关内存泄露。解决方法,在程序不使用ThreadLocal变量时,调用ThreadLocalMap的remove方法移除。