使用场景:
用来解决线程安全问题,如每个线程都有自己的SimpleDateFormat;还可以用来存储每个线程特有的内容,如用户信息,日志记录的traceID,省去传参的麻烦。
原理
如图:每个线程都有一个属性ThreadLocal.ThreadLocalMap threadLocals = null;
明显它是map结构,key是ThreadlLocal本身(this),value是我们用户存储的值。每个线程可以存储多个ThreadLocal值
然后我们看ThreadLocal提供的几个方法,有get() set() remove() withInitial()方法。
public void set(T value) {
//就是获取当前线程的ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//为空就创建,否则直接设置值
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
然后看get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//key即ThreadLocal对应的hashCode是个new AtomicInteger().getAndAdd(0x61c88647),算法目的均匀分配
//这里面还会去删除为null的entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//首次调用,就会执行我们重写的init方法设置初始值,否则返回默认null
return setInitialValue();
}
ThreadLocalMap是一个数组结构
static class ThreadLocalMap {
//注意这里Entry 继承了WeakReference<ThreadLocal<?>
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
}
当key冲突的时候,会寻找下一个空的位置。key即ThreadLocal对应的hashCode是个new AtomicInteger().getAndAdd(0x61c88647),目的是均匀分配。
ThreadLocal的内存泄漏
内存泄漏有两种情况,key的泄漏,或者value的泄漏。
从ThreadLocalMap 的Entry extends WeakReference<ThreadLocal<?>可以看出,key即ThreadLocal继承了弱引用,如果没有强引用指向这个key,也就是只有弱引用时,下次GC就会回收掉key。
正常情况,线程执行结束后,线程t被回收 ,他里面的key和value肯定也会被回收。但是如果线程是在线程池中,线程被重复利用,线程不会死亡,那他的属性ThreadLocalMap 包括里面的value就不会被回收(如果ThreadlLcoal实例置为null,那么key是弱引用会被回收),这时就存在内存泄漏。怎么解决呢?
实际上,Java已经考虑到这个问题,会在get、resize、remove方法中检测如果key为null的entry,会把value设置为null,避免了value被强引用造成内存泄漏。但是如果我们不使用ThreadLocal,也实际上就不会使用到他的get、resize、remove方法,线程又不终止调用链就会一直存在,这样就又导致了value的泄漏。所以我们在使用完ThreadLocal后,必须手动调用remove方法,如拦截器最后finally里调用remove方法。
spring已经为我们提供了很多,如DateTimeContextHolder,RequestContextHolder,尽量使用框架提供的
应用举例
Spring框架中最明显的就是RequestContextHolder,它保存了当前请求参数,常见用法如:
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String authorization = request.getHeader("Authorization");
它包含了两个ThreadLocal<RequestAttributes>
public abstract class RequestContextHolder {
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
// 需要开启才能在子线程中获取父线程的值
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
}
注意Spring框架默认是在DispatcherServlet里(父类FrameworkServlet)的processRequest()里调用了RequestContextHolder.setRequestAttributes()方法来向当前线程的ThreadLocal放入ServletRequestAttributes,这样我们就可以在当前线程里使用RequestContextHolder.getRequestAttributes()了。
除了默认的DispatcherServlet,Spring框架也会在RequestContextListener和RequestContextFilter里去设置setRequestAttributes。