ThreadLocal是Java中的一个线程本地变量类。它可以让每个线程都有自己独立的变量副本,而不会相互影响。
ThreadLocal的使用很简单,其中主要有三个方法
对于ThreadLocal的使用想必大家都了解,但是究竟是怎么设置值、为什么在当前线程中可以获取到设置的值,它是怎么存储的,为什么使用时大家都说会有内存泄漏的隐患呢? 接下来可以带着这些疑惑来来从源码角度分析。
Thread类中维护ThreadLocal.ThreadLocalMap属性,用于存储多个当前线程独有的本地变量值; ThreadLocalMap属性的初始化是在调用ThreadLocal的set(val)方法中完成的 ThreadLocalMap属性获取存储于的变量值是在调用ThreadLocal的get()方法中执行的
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
严格来讲,ThreadLocal类更像是一个工具类,使用它的set(val)方法可以给当前线程设置值,get()获取值,remove()来清除值
public class ThreadLocal<T> {
// 用于给线程创建ThreadLocalMap对象
protected void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 从当前线程中拿到ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// 直接设置值
map.set(this, value);
} else {
// 创建ThreadLocalMap对象并设置值
createMap(t, value);
}
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
...
return result;
}
// ThreadLocalMap是由继承自WeakReference的Entry类型的数组来存储设置本地变量值的,Entry的k就是我们新建的ThreadLocal对象,值为需要存储的值
static class ThreadLocalMap {
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
}
说到底还是用弱引用导致的原因,Java 弱引用(WeakReference) 弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。 当GC时,仅仅会把ThreadLocalMap.Entry中的用(WeakReference)修饰的key给回收掉,然而value还是会被ThreadLocalMap.Entry对象一直引用,导致无法回收; 所以,我们在使用ThreadLocal时要养成好的习惯,使用完之后一定要记得显示调用remove()方法去清除这个对象。
用户登录后,会给客户端下发身份令牌Token,客户端每次请求服务端接口都会携带此Token来标识用户身份, 在此请求贯穿的整个线程生命周期中,我们在任何业务相关逻辑中都可以知道这个用户信息,从而可以记录操作日志等。
public class ApiUserContext {
// 创建存储用户信息的ThreadLocal对象
public static ThreadLocal<ApiUser> curUser = new ThreadLocal<>();
// 返回当前用户ID
public static String getCurUserId() {
return getCurUser().getId();
}
// 返回当前用户
public static ApiUser getCurUser() {
return curUser.get();
}
}
public class JwtAuthCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 读取请求头的token
String token = request.getHeader(ApiAuthConstant.TOKEN);
// 解析校验JWT Token
Claims claims = jwtClientKit.getTokenClaim(token);
ApiUser authedUser = JacksonUtils.toJavaBean(claims.getSubject(), ApiUser.class);
// 将用户信息存储在ThreadLocal中
ApiUserContext.curUser.set(authedUser);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 从ThreadLocal中删除变量
ApiSecurityContext.curUser.remove();
}
}
public class DemoBiz {
public void accessApi() {
ApiUser curUser = ApiUserContext.getCurUser();
log.info("userId = {}, username = {}, 访问接口", curUser.getId(), curUser.getUsername());
}
}
https://github.com/yeeevip/yeee-memo/blob/master/memo-parent/memo-common/common-auth/common-app-auth-client/src/main/java/vip/yeee/memo/common/appauth/client/interceptor/JwtAuthCheckInterceptor.java
对于ToB的支付系统中,需要根据用户所属的租户来获取商户支付配置去调用三方支付接口进行下单,这时可以通过ThreadLocal 设置当前用户请求的支付配置上下文,在调用三方支付接口时可以随时获取达到跨方法的透传
@Slf4j
public class PayContext {
private final String lesseeId;
private final PayProperties payProperties;
private final WxPayConfigBO wxPayConfig;
private final AliPayConfigBO aliPayConfig;
private final static ThreadLocal<PayContext> PAY_CONTEXT_THREAD_LOCAL = new ThreadLocal<>();
public PayContext(String lesseeId, PayProperties payProperties, WxPayConfigBO wxPayConfig, AliPayConfigBO aliPayConfig) {
this.lesseeId = lesseeId;
this.payProperties = payProperties;
this.wxPayConfig = wxPayConfig;
this.aliPayConfig = aliPayConfig;
}
}
public class PayContext {
public static void initContext(String lesseeId) {
try {
PayContext payContext = null;
// 部分代码省略,判断是否为空,不为空才新建PayContext对象
if (payContext == null) {
PayChannelConfigService channelConfigService = (PayChannelConfigService) SpringContextUtils.getBean(PayChannelConfigService.class);
PayProperties payProperties = (PayProperties) SpringContextUtils.getBean(PayProperties.class);
WxPayConfigBO wxPayConfigBO = channelConfigService.getWxPayChannelConfig(lesseeId);
AliPayConfigBO aliPayConfigBO = channelConfigService.getAliPayChannelConfig(lesseeId);
payContext = new PayContext(lesseeId, payProperties, wxPayConfigBO, aliPayConfigBO);
}
// 将支付上下文对象PayContext放置到ThreadLocal中
PAY_CONTEXT_THREAD_LOCAL.set(payContext);
} catch (Exception e) {
log.error("初始化支付上下文失败", e);
throw new BizException("初始化支付上下文失败");
}
}
}
public class UnifiedPayOrderService {
public UnifiedOrderRespBO unifiedOrder(UnifiedOrderReqVO reqVO) throws Exception {
try {
// 初始化设置当前用户线程支付上下文
PayContext.initContext(reqVO.getLesseeId());
UnifiedOrderRespBO respBO = wxAppPayKit.unifiedOrder(reqBO);
return respBO;
} finally {
// 清除ThreadLocal对象
PayContext.clearContext();
}
}
}
public class WxAppPayKit extends BaseWxPayKit {
@Override
public UnifiedOrderRespBO unifiedOrder(UnifiedOrderReqBO reqBO) {
try {
// 通过PayContext从ThreadLocal中获取当前租户支付配置
PayContext payContext = PayContext.getContext();
Config config = new Config(payContext);
wxPayService.setConfig(config);
UnifiedOrderRespBO respBO = wxPayService.createOrder(reqBO);
...
...
return respBO;
} catch (Exception e) {
log.info("【统一下单-微信APP支付】- 下单失败 reqBO = {}", reqBO, e);
throw new BizException(e.getMessage());
}
}
}
https://github.com/yeeevip/yeee-memo/blob/master/third-sdk/third-pay/src/main/java/vip/yeee/memo/demo/thirdsdk/pay/paykit/PayContext.java
版权 本文为yeee.vip原创文章,转载无需和我联系,但请注明来自https://www.yeee.vip
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。