线程重用导致用户信息错乱--Threadlocal

线程重用导致用户信息错乱

1.业务案例

  ThreadLocal 适用于变量在线程间隔离,而在方法或类间共享的场景。如果用户信息的获取比较昂贵(比如从数据库查询用户信息),那么在 ThreadLocal 中缓存数据是比较合适的做法。但为什么会出现用户信息错乱的Bug呢?

  // imitate saveUserInfo
    private static final ThreadLocal currentUser = ThreadLocal.withInitial(() -> null);
		
    public static HashMap wrong(Integer userId) {
        //设置用户信息之前先查询一次ThreadLocal中的用户信息
        String before  = Thread.currentThread().getName() + ":" + currentUser.get();
        //设置用户信息到ThreadLocal
        currentUser.set(userId);
        //设置用户信息之后再查询一次ThreadLocal中的用户信息
        String after  = Thread.currentThread().getName() + ":" + currentUser.get();
        //汇总输出两次查询结果
        HashMap result = new HashMap();
        result.put("before", before);
        result.put("after", after);
        return result;
    }
		
    public static void main(String[] args) {
    		//通过线程池方式模拟tomcat服务下,多个请求同时访问,
        ExecutorService executorService =  new ThreadPoolExecutor(
                2,4,1L,TimeUnit.SECONDS, new LinkedBlockingQueue<>());

        for (int i =0 ; i < 5 ; i ++){
             //用户id
            int userId = 10000+i;
            executorService.execute(() -> System.out.println(wrong(userId)));
        }

    }

2.原理分析

按理说,在设置用户信息之前第一次获取的值始终应该是 null,但我们要意识到,而 Tomcat 的工作线程是基于线程池。

因为线程的创建比较昂贵,所以 Web 服务器会使用线程池来处理请求,这就意味着线程会被重用。一旦线程重用,那么很可能首次从 ThreadLocal 获取的值是之前其他用户的请求遗留的值。

注意:使用类似 ThreadLocal 工具来存放一些数据时,需要特别注意在代码运行完后,显式地去清空设置的数据。如果在代码中使用了自定义的线程池,也同样会遇到这个问题。

3.修改方案

     每次使用完,手动清除

public static HashMap wrong(Integer userId){
        String before = Thread.currentThread().getName() + ":" + currentUser.get();
        currentUser.set(userId);
        try { String after = Thread.currentThread().getName() + ":" + currentUser.get();
            HashMap result = new HashMap();
            result.put("before", before);
            result.put("after", after);
            return result;
        }
        finally { //在finally代码块中删除ThreadLocal中的数据,确保数据不串
             currentUser.remove();
        }
    }

 

你可能感兴趣的:(Java常见业务开发错误案例,Threadlocal)