线程池中父子线程数据传递的问题

使用线程池执行任务时,子线程也需要获取用户信息,因为我们使用的是spring的security框架,取出用户信息的方法如下:


 User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 

子线程调用以上方法取出的user信息为null,为什么为null,看下源码发现用户信息是从当前线程的threadlocal中取的,只有主线程设置过user信息

//SecurityContextHolder.getContext()源码
SecurityContext ctx = (SecurityContext)contextHolder.get();
//get方法最终调用了threadlocal的get方法
 public T get() {
        Thread t = Thread.currentThread();
        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();
    }

那怎么把user信息从主线程传到子线程中呢,这里我们可以直接使用spring提供的TaskDecorator来解决

TaskDecorator使用了装饰器模式,在初始化线程池的时候复写了线程池的execute方法

ThreadPoolExecutor executor;
		if (this.taskDecorator != null) {
			executor = new ThreadPoolExecutor(
					this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
					queue, threadFactory, rejectedExecutionHandler) {
				@Override
				public void execute(Runnable command) {
					Runnable decorated = taskDecorator.decorate(command);
					if (decorated != command) {
						decoratedTaskMap.put(decorated, command);
					}
					super.execute(decorated);
				}
			};
		}

所以我们可以新建一个TaskDecorator类,复写decorate方法,设置user信息,源码如下

//初始化线程池
 @Bean(name = "executor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数:线程池创建时候初始化的线程数
        executor.setCorePoolSize(10);
        // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(20);
        // 缓冲队列:用来缓冲执行任务的队列
        executor.setQueueCapacity(500);
        // 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        // 等待其他所有的任务执行完毕之后再关闭线程池(如redis,mysql)
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //设置TaskDecorator
        executor .setTaskDecorator(new ContextCopyingDecorator());
        //设置线程池中任务的超等待时间,超时则强制销毁
        executor.setAwaitTerminationSeconds(60);
        // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("assembling-");
        // 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return threadPoolTaskExecutor;
    }

	//自定义的TaskDecorator
    static class ContextCopyingDecorator implements TaskDecorator {
        @Nonnull
        @Override
        public Runnable decorate(@Nonnull Runnable runnable) {
            SecurityContext securityContext = SecurityContextHolder.getContext();
            return () -> {
                try {
                    SecurityContextHolder.setContext(securityContext);
                    runnable.run();
                } finally {
                    SecurityContextHolder.clearContext();
                }
            };
        }

记住线程执行完成后一定要clearContext

到这里基本就可以了

不过这里还有一个大坑,

这个坑会导致在执行任务数过多的时候,线程取出来的uer信息依然是null

提示:大家可以留意下上图在初始化线程池的时候设置的拒绝策略

有发现问题的可以留言告诉我哦~

下一期我会结合初始化线程池的源码来分析这个坑

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值