ThreadLocal 场景题

简介: ThreadLocal 场景题

面试中谈起并发,ThreadLocal 必定要聊。ThreadLocal 的线程隔离非常好用。

几个经典且实用的场景,防止在面试官前一时语塞

1. 用过ThreadLocal吗?

2. 它有什么用?

3. 什么场景下使用的?

场景一、保存用户信息

当我们的系统项目足够大到一定程度时,一些基础功能模块被调用很多次也会随之增大。

例如用户模块,几乎每个系统的每个功能都是和用户绑定的,用户在访问某个功能时,总是需要判断用户的一些属性判断对应是否拥有对应权限。


如果每个功能都查询一遍用户及相关信息,那代码就过于臃肿且不利于维护。


场景:

接手一个项目,用户信息是用Feign调用用户中心获取,方法之间相互调用使用用户信息很麻烦(1,通过接口传参,2,再次调用Feign)。所以使用ThreadLocal存放用户信息。(大多公司都会把自己的用户单独放到公司层面做一套服务,供各个业务线使用)

1,定义用户实体:
@ApiModel("登录用户信息")
@Data
public class FeginUser implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "用户ID")
    private String id;
    @ApiModelProperty(value = "用户名")
    private String name;
   
    @ApiModelProperty(value = "密码")
    @JsonIgnore
    private String upass;
}
2,定义工具类操作ThreadLocal(存放,获取,删除用户信息)
  public class ThreadLocalUtil {

        /**
         * 保存用户对象的ThreadLocal  在拦截器操作 添加、删除相关用户数据
         */
        private static final ThreadLocal<FeginUser> userThreadLocal = new ThreadLocal<FeginUser>();

        /**
         * 添加当前登录用户方法  在拦截器方法执行前调用设置获取用户
         * @param user
         */
        public static void addCurrentUser(FeginUser user){
            userThreadLocal.set(user);
        }

        /**
         * 获取当前登录用户方法
         */
        public static FeginUser getCurrentUser(){
            return userThreadLocal.get();
        }


        /**
         * 删除当前登录用户方法  在拦截器方法执行后 移除当前用户对象
         */
        public static void remove(){
            userThreadLocal.remove();
        }
    }
3,拦截器:1,访问接口时将用户信息放入ThreadLocal,2,访问结束时候删除ThreadLocal中信息(线程放入线程池并不一定会销毁)
@Component
@Slf4j
public class UserInfoInterceptor implements HandlerInterceptor  {

    @Autowired
    private UserInfoUtil userInfoUtil;

    /**
     * 请求执行前执行的,将用户信息放入ThreadLocal
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        FeginUser user;
        try{
             user = userInfoUtil.getUser();
        }catch (CustomException e){
            log.info("***************************用户未登录, ThreadLocal无信息***************************");
            return true;
        }
        if (null!=user) {
            log.info("***************************用户已登录,用户信息放入ThreadLocal***************************");
            ThreadLocalUtil.addCurrentUser(user);
            return true;
        }
        log.info("***************************用户未登录, ThreadLocal无信息***************************");
        return true;
    }

    /**
     * 接口访问结束后,从ThreadLocal中删除用户信息
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("***************************接口调用结束, 从ThreadLocal删除用户信息***************************");
        ThreadLocalUtil.remove();
    }

4,配置拦截器。
@Configuration
@ComponentScan
public class MyAppConfigurer extends WebMvcConfigurationSupport {

    @Autowired
    private UserInfoInterceptor userInfoInterceptor;

    /**
     * 拦截器,将用户信息放入threadLocal
     *
     * @param registry
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.userInfoInterceptor).addPathPatterns("/**");
        super.addInterceptors(registry);
    }

}

5,定义用户信息具体操作接口:

为了类实现接口后直接使用(不定义为基础类,是因为类单继承)

publicpublic interface IBaseUserInfo {
    default Boolean isLogin() {
        return ThreadLocalUtil.getCurrentUser() != null;
    }

    default FeginUser getUser() {
        return ThreadLocalUtil.getCurrentUser();
    }

    default String getUserId() {
        if (ThreadLocalUtil.getCurrentUser() != null) {
            return ThreadLocalUtil.getCurrentUser().getId();
        }
        return null;
    }

}

6,使用:(代码中第二种使用方法,不需要定义接口)
@Service
public class ApplylServiceImpl implements IBaseUserInfo {
    

    public void applyUserInfo() {
        /**
         * 1,实现接口后,直接使用
         */
        FeginUser user1 = getUser();
        /**
         * 2,不实现接口,调用ThreadLocalUtil
         */
        FeginUser user2 = ThreadLocalUtil.getCurrentUser();
    }

}

4. 源码深入

目录
相关文章
|
监控 NoSQL Java
场景题:百万数据插入Redis有哪些实现方案?
场景题:百万数据插入Redis有哪些实现方案?
164 1
场景题:百万数据插入Redis有哪些实现方案?
|
7月前
|
缓存 监控 NoSQL
场景题:线上接口响应慢,应该如何排查问题?
面试中常见的接口响应慢排查题旨在考察研发人员的系统性解决问题的能力。回答时需结合业务场景(如大促、高峰期),并运用工具(Arthas、SkyWalking等)进行监控告警、链路追踪和日志分析,明确问题范围及原因。具体步骤包括:1. 定位问题(确认单个接口或整体系统、查看APM指标、分析链路和日志);2. 排查网络、中间件及外部依赖(检测延迟、检查Redis、RocketMQ、MySQL等);3. 服务端性能分析(CPU、内存、磁盘IO、JVM调优)。最后提出优化方案,如代码逻辑、数据库、缓存策略及资源扩容等。总结时可结合实际案例,展示完整的排查与优化流程。
1185 3
|
8月前
|
人工智能 Cloud Native 虚拟化
从铜线到云端:网络技术的跨越与未来趋势
本文围绕物理网络和云网络基础知识科普进行展开,最后浅谈在AI大模型浪潮下云网络的演进方向。
508 130
|
7月前
|
算法 NoSQL Java
场景题:10亿QQ用户,如何统计在线人数?
在竞争激烈的就业市场中,面试不仅考察八股文、算法和项目经验,场景题也愈发重要。本文介绍Java面试中的“在线人数统计”问题:面对亿级用户,如何高效统计在线人数。传统数据库方案难以应对频繁的上线下线操作带来的IO压力,而使用Bitmap(位数组)或Redis的Bitmap命令则能有效解决这一问题。通过将每个用户的在线状态映射到位数组中,仅需119.2MB内存即可处理10亿用户,在线人数统计变得简单高效。
280 9
|
7月前
|
存储 大数据 BI
场景题:有40亿个QQ号如何去重?仅1GB内存
在处理大数据去重问题时,如40亿QQ号的去重(仅1GB内存),可采用Bitmap和布隆过滤器两种方法。Bitmap利用位图存储,每个QQ号占1位,总需512MB内存,适用于整型数据;布隆过滤器通过多个哈希函数计算下标,适合字符串或对象去重,但存在误判率。在线人员统计等场景也可使用类似思路,将ID作为偏移值标记在线状态或视频存在性。
255 3
|
人工智能 小程序 前端开发
小程序二手商城|使用Springboot+vue+微信小程序开发校园二手商城系统
本项目基于Springboot+vue+微信小程序实现了一个校园二手物品商城交易系统。系统的后台使用springboot+mybatis开发实现,后台管理页面使用Vue+ElementUI开发实现,用户端基于微信小程序开发实现。前端用户使用微信登录小程序后,可以在线浏览二手商品,并在线购买下单和评论等,同时自己也可以发布相应的二手商品,并管理自己的订单信息和销售信息。管理员登录后台管理系统可以管理人员、商品分类、商品、订单等相关信息。具体见下面展示。
545 0
|
负载均衡 Java 测试技术
性能测试与负载均衡:保证Java应用的稳定性
性能测试与负载均衡:保证Java应用的稳定性
|
数据采集 存储 监控
使用Java构建实时监控和警报系统的最佳实践
使用Java构建实时监控和警报系统的最佳实践
|
测试技术 程序员 Linux
【Docker项目实战】使用Docker部署blog轻量级博客系统
【2月更文挑战第16天】使用Docker部署blog轻量级博客系统
765 2
|
NoSQL 算法 关系型数据库
Redis系列-16.腾讯经典面试题-如何做一个迷你版的微信抢红包呢?
Redis系列-16.腾讯经典面试题-如何做一个迷你版的微信抢红包呢?
323 0