被用作在同一个线程中存取信息的对象;该对象存储的信息仅在该线程中可以访问,做到了线程间隔离。
常用在当请求进入拦截器时,在该请求线程中存入一些信息(如根据用户请求确定其用户的一些基本信息并存入,方便该线程的方法随时取出利用),在请求结束时调用其 remove()
方法清空信息。
每个 Thread
内都会个 ThreadLocal.ThreadLocalMap threadLocals = null
对象
public class Thread implements Runnable {
private static native void registerNatives();
static {
registerNatives();
}
// ...
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
// ...
}
ThreadLocalMap
是 ThreadLocal
的静态内部类,也就意味着这个 ThreadLocalMap
是应用中公用的。
ThreadLocalMap
内部存储结构类似于 Map.Entry
,而这个 Entry
结构的 key
是 WeakReference
,这也是会发生内存泄漏的原因(后文解释)
public class ThreadLocal {
// ...
static class ThreadLocalMap {
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
// ...
}
它会在当前线程第一次调用的时候初始化(当 threadlocals == null
)
// 初始化
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal>) this);
}
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
实际操作是在每次调用 ThreadLocal
方法时都会传入当前的线程,根据这个线程找到这个线程对应的ThreadLocalMap
,而ThreadLocalMap
内的 Entry
属性,这就是用来存储这个线程公用的 key,value
。也就是说 ThreadLocal
是操作 ThreadLocalMap
的桥梁。
每个 ThreadLocalMap
都是这个线程独有的,所以是线程隔离的。
// get方法示例
public T get() {
Thread t = Thread.currentThread();
// 传入当前线程作参数,得到对应的 ThreadLocal
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
@Data
public class ThreadLocalUser {
/**
* 用户id
*/
private Integer userId;
/**
* 用户昵称
*/
private String nickname;
/**
* 用户头像地址
*/
private String avatarUrl;
/**
* 用户权限集合
*/
private List authorities;
}
ThreadLocal
类/**
* ThreadLocal用户信息管理对象
*/
@Getter
@Setter
public class RequestThreadHolder {
private final static String USERID = "userId";
private final static String NICKNAME = "nickname";
private final static String ROLES = "authorities";
private final static ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(ThreadLocalUser::new);
/**
* 根据请求的jwt解析用户信息初始化请求的用户信息
* @param jwt
*/
public static void init(String jwt){
DecodedJWT decode = JWT.decode(jwt);
Map claims = decode.getClaims();
Integer userId = Objects.isNull(claims.get(USERID)) ? 0 : claims.get(USERID).asInt();
String nickname = StringUtils.isEmpty(claims.get(NICKNAME).asString()) ? "佚名" : claims.get(NICKNAME).asString();
List roles = Objects.isNull(claims.get(ROLES)) ? Collections.emptyList() : claims.get(ROLES).asList(String.class);
THREAD_LOCAL.get().setUserId(userId);
THREAD_LOCAL.get().setNickname(nickname);
THREAD_LOCAL.get().setAuthorities(roles);
}
/**
* 获取请求用户信息
* @return
*/
public static ThreadLocalUser getUserInfo(){
return THREAD_LOCAL.get();
}
/**
* 获取请求用户id
* @return
*/
public static Integer getLocalUserId(){
return THREAD_LOCAL.get().getUserId();
}
/**
* 获取请求用户的昵称
* @return
*/
public static String getLocalUserNickname(){
return THREAD_LOCAL.get().getNickname();
}
/**
* 获取请求用户角色列表
* @return
*/
public static List getLocalUserRoles(){
return THREAD_LOCAL.get().getAuthorities();
}
/**
* 清除ThreadLocal信息
*/
public static void clean(){
THREAD_LOCAL.remove();
}
}
/**
* 请求用户信息拦截器
*/
public class RequestThreadHolderInterceptor extends HandlerInterceptorAdapter {
/**
* 请求进入前向 ThreadLocal 注入用户信息
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String bearerToken = request.getHeader(HttpHeaders.AUTHORIZATION);
String jwt = StringUtils.substringAfter(bearerToken,TokenConstant.TOKEN_PREFIX);
RequestThreadHolder.init(jwt);
if (StringUtils.isEmpty(jwt)){
throw new InnerException("token不存在");
}
return super.preHandle(request, response, handler);
}
/**
* 请求结束后调用清理 ThreadLocal
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
RequestThreadHolder.clean();
super.afterCompletion(request, response, handler, ex);
}
}
@Configuration
public class InterceptorConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestThreadHolderInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/swagger-resources/**", "/v2/**", "/test/**");
}
}
内存泄漏:内存中存在没有被程序使用的对象,但是无法被垃圾回收器回收。
ThreadLocal
的内存泄漏涉及到 Java
的弱引用。
弱引用/软引用解释:
对于以下的各类引用,new出来的只是对传入进去的对象声明一个弱/软引用,当这个对象还存在强引用时,即使显示调用System.gc()也无法回收该对象。
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
WeakReference<Object> weak2 = new WeakReference<>(object);
System.out.println("weak:" + weak2.get()); //输出object对象
object = null;
System.gc();
//调用gc:输出null;否则输出object对象。
//因为若不执行object=null,则new object()这个对象一直存在强引用,无法回收。
System.out.println("weak gc:" + weak2.get());
}
1.强引用:
强引用是使用最普遍的引用。如:Object obj = new Object();
这时对这个Object对象就存在一个强引用。对于强引用如果一直存在,GC不会对其进行回收,即使内存溢出。所以如果对象确定不在使用,需要及时弱化,如:obj = null;
2.软引用(SoftReference):
一个对象若只有软引用,则内存空间充足时,GC就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
String str = new String("abc");
//声明一个软引用对象,引用的是new String("abc")
SoftReference<String> softReference = new SoftReference<String>(str);
3.弱引用(WeakReference):
//对于弱引用,GC不管当前内存空间足够与否,都会回收它的内存。
String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str);
4.虚引用(PhantomReference):
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
弱引用gc简单测试
public static void main(String[] args) {
User user1 = new User();
user1.setUsername("张三");
User user2 = new User();
user2.setUsername("李四");
WeakReference<User> weak1 = new WeakReference<>(user1);
WeakReference<User> weak2 = new WeakReference<>(user2);
HashMap<WeakReference<User>, Integer> map = new HashMap<>();
map.put(weak1,1);
map.put(weak2,2);
user1 = null;
System.gc();
for (Map.Entry entry:map.entrySet()){
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
构建弱引用对象
@Getter
@Setter
// 继承弱引用对象,并指定泛型(非必须)
public class WeakRefUser extends WeakReference {
private String name;
// WeakReference的构造必须初始化referent属性,即标明这个对象是弱引用对象
public WeakRefUser(User referent,String name) {
super(referent);
this.name = name;
}
}
代码测试
public static void main(String[] args) {
WeakRefUser weakRefUser1 = new WeakRefUser(new User(), "张三");
WeakRefUser weakRefUser2 = new WeakRefUser(new User(), "李四");
HashMap map = new HashMap<>();
map.put(weakRefUser1,1);
map.put(weakRefUser2,2);
System.gc();
System.out.println();
}
ThreadLocal 内部构造查看
ThreadLocal
的方法实际是操作其内部属性 ThreadLocalMap
;而 ThreadLocalMap
的主要结构其实是一个弱引用的 Entry
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
// ...
}
看到上述结构就能明白,平时我们操作的 Map
结构就是一个 Entry
;而 ThreadLocalMap
中 Entry
的 key
是弱引用的 referent
属性,这个属性只要经过 gc
就会被回收,使其指向 null
;这就会导致这个 Entry
中存在 key
为 null
的但其 Entry
与ThreadLocalMap(静态类)
存在强关联,使得这样的 Entry
无法被 gc
回收,而这种元素又是无法被利用的,如果不及时清理,使这种类型元素大量堆积,就出现常说的内存泄漏(存在大量无法引用的对象)
避免 ThreadLocal 内存泄漏
ThreadLocal
在调用 get()
、set()
、remove()
时都会处理这些为 null
的 key
,但依旧建议在使用完及时 remove()
来保证一定移除这个 Entry
。
InheritableThreadLocal
跟 ThreadLoca
l区别是 可以传递值给子线程。
存在的问题:
ThreadLocalMap
给子线程了。TransmittableThreadLocal
阿里开源的,很好的解决 InheritableThreadLocal
在线程池情况下,父子线程值传递问题。