ThreadLocal详解
一、对ThreadLocal(线程局部变量)的理解
public void set(T value) {
Thread t = Thread.currentThread();//获得当前的线程
ThreadLocalMap map = getMap(t);//获得当前线程的Map(用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本)
if (map != null)
map.set(this, value);//如果map存在的话,就将当前ThreadLocal对象作为key,需要保存的变量作为value,存放在map
else
createMap(t, value);//如果map不存在,就初始化map
}
2、返回当前线程对应的线程局部变量
public T get() {
Thread t = Thread.currentThread();//获得当前线程
ThreadLocalMap map = getMap(t);//获得当前线程的Map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//获得key(当前ThreadLocal对象)对应的entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;//读取entry对应的value
return result;
}
}
return setInitialValue();
}
3、返回当前线程Map中key=当前ThreadLocal对象对应value的初始值
protected T initialValue() {
return null;
}
在线程通过get()方法获得变量的时候initialValue()会被第一次调用
如果在get()方法前,先调用了set()方法,那么initialValue()不会被调用
4、删除当前线程Map中key=当前ThreadLocal对象对应value的初始值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
}
5、(特别注意)ThreadLocalMap中Entry的key是软引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
软引用
WeakReference是Java语言规范中为了区别直接的对象引用(程序中通过构造函数声明出来的对象引用)而定义的另外一种引用关系。
WeakReference标志性的特点是:reference实例不会影响到被应用对象的GC回收行为(即只要对象被除WeakReference对象之外所有的对象解除引用后,该对象便可以被GC回收),只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null
理解 : ThreadLocalMap中的key是弱引用,不会影响到threadLocal的GC行为。如果是强引用的话,在线程运行过程中,我们不再使用threadLocal了,将threadLocal置为null,但threadLocal在线程的ThreadLocalMap里还有引用,导致其无法被GC回收(当然,可以等到线程运行结束后,整个Map都会被回收,但很多线程要运行很久,如果等到线程结束,便会一直占着内存空间)。而key声明为WeakReference,threadLocal置为null后,线程的threadLocalMap就不算强引用了,threadLocal就可以被GC回收了。map的后续操作中,也会逐渐把对应的"stale entry"清理出去,避免内存泄漏。
所以,我们在使用完ThreadLocal变量时,尽量用threadLocal.remove()来清除,避免threadLocal=null的操作。前者remove()会同时清除掉线程threadLocalMap里的entry,算是彻底清除;而后者虽然释放掉了threadLocal,但线种threadLocalMap里还有其"stale entry",后续还需要处理。
三、应用场景
ThreadLocal的典型用途是提供上下文信息,比如在一个Web服务器中,一个线程执行用户的请求,在执行过程中,很多代码都会访问一些共同的信息,比如请求信息、用户身份信息、数据库连接、当前事务等,它们是线程执行过程中的全局信息,如果作为参数在不同代码间传递,代码会很啰嗦,这时,使用ThreadLocal就很方便,所以它被用于各种框架如Spring中,我们看个简单的示例:
public class RequestContext {
public static class Request { //...
};
private static ThreadLocal<String> localUserId = new ThreadLocal<>();
private static ThreadLocal<Request> localRequest = new ThreadLocal<>();
public static String getCurrentUserId() {
return localUserId.get();
}
public static void setCurrentUserId(String userId) {
localUserId.set(userId);
}
public static Request getCurrentRequest() {
return localRequest.get();
}
public static void setCurrentRequest(Request request) {
localRequest.set(request);
}
}