感觉ThreadLocal这家伙面试必问,但可能使用上确没怎么用,不过其实还是很有用处的。
1.使用方法:
使用如ThreadLocal<String> localVar = new ThreadLocal<>();
有T get()、set(T value)、remove()等方法。
2.Threadlocal如何实现线程安全?:
Threadlocal里面有一个内置静态类ThreadLocalMap,是一个类似HashMap的,
然后在Thread类中有一个ThreadLocal.ThreadLocalMap threadLocals 属性,
每次操作ThreadLocal(get和set)对象时其实是操作的各个线程内部的ThreadLocalMap (key是这个ThreadLocal对象本身,value是值),即使ThreadLocal变量是定义在公共地方,多线程操作这个变量也是相互隔离互不影响的。
3.Threadlocal如何用弱引用WeakReference防止内存泄漏?
ThreadLocal.ThreadLocalMap里面有一个静态内部类Entry,而ThreadLocal.ThreadLocalMap的元素就是Entry,而Entry extends WeakReference<ThreadLocal<?>>,
然后构造方法:
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
可见key是一个弱引用,因为key就是Threadlocal对象本身,而弱引用在gc时会直接被回收而不管内存是否够用,那会不会说Threadlocal很快被回收下次再get就为null了?其实不是,Threadlocal定义的地方(new的地方)有一个强引用,只要强引用还在,在堆中Threadlocal的变量就不会被回收,key定义成弱引用,是因为若Threadlocal定义的地方被回收了(所在线程结束了或手工 tl == null),若key还是强引用(且线程还存在),则这个变量还是不能回收的,定义成弱引用才会被回收。
问题: 若key被回收了,那value没被回收不是也会内存泄漏吗? 是的,value也有可能会造成内存泄漏,但value没有其他强引用若也定义成弱引用则下次就get不到了拿到值会是null。所以除非我们知道线程会被结束回收掉(线程被回收,其变量ThreadLocalMap也会被回收就不会有泄漏),否则最好我们在用完Threadlocal直接调下remove()方法释放内存,比如有公司就出现过使用Threadlocal产生内存泄漏最终内存溢出,因为他用的是线程池,线程一直存在不会销毁所以其下ThreadLocalMap的元素会越累越多。
4.有应用的地方:
Spring事务实现: spring事务实现就是基于数据库的事务,比如mysql,比如@Transactional是采用AOP 反射实现事务,底层是保证同一个数据库连接来保证事务的,如果同一个线程内多次操作数据库,每次都是从连接池拿不同连接会很浪费性能,所以同一个线程里面都是用同一个数据库连接的,但如何保证同一个数据库连接呢?其实就是用Threadlocal来保证。
最常见的 ThreadLocal 使用场景为用来解决数据库连接、Session 管理等。数据库连接和 Session 管理涉及多个复杂对象的初始化和关闭。如果在每个线程中声明一些私有变量来进行操作,那这个线程就变得不那么“轻量”了,需要频繁的创建和关闭连接。
5.ThreadLocal的一些子类:
ThreadLocal<T>的实现:
NamedThreadLocal:spring的,只是比ThreadLocal多一个name属性。
InheritableThreadLocal: jdk的,能解决父子线程的值传递,但解决不了线程池里的值传递。
NamedInheritableThreadLocal:spring的,只是比InheritableThreadLocal多一个name属性。
TransmittableThreadLocal:alibaba的,继承InheritableThreadLocal,能解决线程池间的值传递,但解决不了并行流里的值传递。
6.说下并行流parallelStream:
并行流是基于forkjoin。工作被拆成细小的worker,不会阻塞线程,性能要比普通线程池高,但无法传递threadLocal的值。
那如何解决并行流里的值传递呢?
解决办法:
使用TransmittableThreadLocal,并且,
在启动的VM参数加上:
-javaagent:D:/programfiles/apache-maven-3.6.3/m2/myrepository/com/alibaba/transmittable-thread-local/2.12.2/transmittable-thread-local-2.12.2.jar
7.TransmittableThreadLocal保存用户上下文例子:
当然,除了保存用户信息,还可用作保存权限相关信息,国际化语言环境信息等(一般通过cookie传入)。
①一个UserContext类:
private static final TransmittableThreadLocal<User> USER_INFO = new TransmittableThreadLocal<>();
public static void setUser(User user){
USER_INFO.remove();
USER_INFO.set(user);
}
public static User getUser(){
return USER_INFO.get();
}
public static void clear(){
USER_INFO.remove();
}
②自定义一个Filter初始化ttl:
public class MyFilter extends OncePerRequestFilter
在其下doFilterInternal()方法中执行
UserContext.setUser(user);【当然user是根据token拿到的】
(这个MyFilter 可以在
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
中执行
http.addFilterBefore(new MyFilter (), UsernamePasswordAuthenticationFilter.class);
或者直接将MyFilter 定义成bean
两种方法都能使得MyFilter 生效。
)
③自定义拦截器,在请求结束后清除ttl变量。(一般对ttl建议写下清除好习惯更为保障,避免内存泄漏情况)
@Component
public class AuthInterceptor implements AsyncHandlerInterceptor {
/**
* 请求结束,清除TTL中的身份信息
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex){
UserContext.clear();
}
}
注册拦截器:
@Configuration
public class AppConfiguration implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor);
}
}