ThreadLocal - JiyangM/spring GitHub Wiki
ThreadLocal是在JDK包里面提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作的自己本地内存里面的变量,从而避免了线程安全问题,创建一个ThreadLocal变量后每个线程会拷贝一个变量到自己本地内存.
源码实现,当set值的时候通过Thread.currentThread() 得到当前的线程,然后通过一个getMap(t) 的方法 获取和当前线程相关的TheadLocalMap
设置value时,要根据当前线程t获取一个ThreadLocalMap类型的map,真正的value保存在这个map中。这验证了之前的一部分想法——ThreadLocal变量保存在一个“线程相关”的map中。
直接看set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); // 使用this引用作为key,既做到了变量相关,又满足key不可变的要求。
else
createMap(t, value);
}
希望进一步深入,可以继续查看ThreadLocal.ThreadLocalMap类的源码:
/**
* 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 {
/**
* 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); // 使用了WeakReference中的key
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
ThreadLocal 导致的内存泄露问题
https://blog.csdn.net/xiaolyuh123/article/details/79869659 https://juejin.im/post/59db31c16fb9a00a4843dc36
使用:
小程序案例,用户请求安全认证,拦截器继承HandlerInterceptorAdapter,preHandle中做自动登录,并把登录后的信息手机号、openid存在ThreadLocal,提供get方法从ThreadLocal中取值,提供 remove方法清除当前线程的threadLoacl变量,防止内存 泄露。get方法的主要调用时机是在controller中可以直接获取手机号码
public class AuthHandlerInterceptor extends HandlerInterceptorAdapter {
private AuthHandler authHandler;
public AuthHandlerInterceptor(AuthHandler authHandler) {
this.authHandler = authHandler;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
AssertAuth assertAuth = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), AssertAuth.class);
if (assertAuth == null) {
return true;
}
boolean autoLogin = authHandler.autoLogin(request);
if (!autoLogin) {
throw new InvalidAuthException();
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
SecurityContextHolder.clearContext();
}
}
public class SecurityContextHolder {
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();
public static void setContext(SecurityContext context) {
Verify.notNull(context, "Only non-null SecurityContext instances are permitted");
contextHolder.set(context);
}
public static SecurityContext getContext() {
return contextHolder.get();
}
public static void clearContext() {
contextHolder.remove();
}
}
谨慎使用-内存泄露
如果任务对象结束而线程实例仍然存在(常见于线程池的使用中,需要复用线程实例),那么仍然会发生内存泄露。
当一个线程调用ThreadLocal的set方法设置变量时候,当前线程的ThreadLocalMap里面就会存放一个记录,这个记录的key为ThreadLocal的引用,value则为设置的值。如果当前线程一直存在而没有调用ThreadLocal的remove方法,并且这时候其它地方还是有对ThreadLocal的引用,则当前线程的ThreadLocalMap变量里面会存在ThreadLocal变量的引用和value对象的引用是不会被释放的,这就会造成内存泄露的。但是考虑如果这个ThreadLocal变量没有了其他强依赖,而当前线程还存在的情况下,由于线程的ThreadLocalMap里面的key是弱依赖,则当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用会被在gc的时候回收,但是对应value还是会造成内存泄露,这时候ThreadLocalMap里面就会存在key为null但是value不为null的entry项。其实在ThreadLocal的set和get和remove方法里面有一些时机是会对这些key为null的entry进行清理的,但是这些清理不是必须发生的,下面简单说下ThreadLocalMap的remove方法的清理