异步线程子线程中获取不到主线程ThreadLocal中信息的分析与解决

项目中使用了ThreadLocal保存用户登录后的信息

public class RequestContextProxy {

    private static final ThreadLocal<Map<String, UserContext>> threadLocal = new ThreadLocal<>();
    private static final String CONTEXT_KEY = "current_user";

    public static void setContext(UserContext context) {
        put(CONTEXT_KEY, context);
    }

    public static UserContext getContext() {
        Map<String, UserContext> map = getContextMap();
        if (Objects.isNull(map)) {
            return null;
        }
        return getContextMap().get(CONTEXT_KEY);
    }
}

问题:用户导入excel数据,入库后发现没有修改更新人信息

处理excel数据入库信息采用了异步处理方式,@Async

 @Async
 public void importModifyBusiness(FileUploadDto fileUploadDto, String fileKey, String createUserId) {    
     // 业务代码...
     RequestContextProxy.getContext();
}

通过调试发现异步代码中的RequestContextProxy.getContext()获取到值为null,所以导致用户值不会更新。深究其原因是异步线程和主线程为2个线程(通过Thread.currentThread()插桩即可判断),用户的登录信息是保存在主线程的ThreadLocal中,各线程的ThreadLocal是独立的,我们从异步线程中是获取不到的。解决方法有以下2种:

1.从主线程直接获取信息后传入异步线程(子线程),接口传入异步方法,不再赘述

2.异步线程中设置主线程信息

下面着重简介下第二种配置方式

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("asyncTaskThread-");
        executor.setCorePoolSize(50);
        executor.setQueueCapacity(0);
        executor.setMaxPoolSize(200);
        executor.setKeepAliveSeconds(60);
        // 异步Task装饰器,传递主线程上下文至异步线程
        executor.setTaskDecorator(new AsyncContextDecorator());
        //线程占满的时候如何处理,简单记个日志
        executor.setRejectedExecutionHandler((r, executor1) -> log.error("Task queue is full,reject {}", executor1.getActiveCount()));
        //这句必须,否则没有初始化无法使用
        executor.initialize();
        return executor;
    }
}

主要是这个配置executor.setTaskDecorator(new AsyncContextDecorator()),先获取主线程中信息然后在异步线程开始前将信息设置进去,这样在异步线程中就拷贝了主线程信息了。

public class AsyncContextDecorator implements TaskDecorator {

    @Override
    @Nonnull
    public Runnable decorate(@Nonnull Runnable runnable) {
        // 获取主线程中的请求信息(用户信息也放在里面)
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        UserContext userContext = RequestContextProxy.getContext();
        return () -> {
            try {
                // 将主线程的请求信息,设置到子线程中
                RequestContextHolder.setRequestAttributes(attributes);
                RequestContextProxy.setContext(userContext);
                // 执行子线程
                runnable.run();
            } finally {
                // 线程结束,清空信息,否则可能造成内存泄漏
                RequestContextHolder.resetRequestAttributes();
                RequestContextProxy.clear();
            }
        };
    }

}

  • 13
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用Python实现多线程域名搜集的示例代码: ```python import threading import queue import socket # 定义线程数 THREAD_NUM = 10 # 定义目标域名 TARGET_DOMAIN = 'example.com' # 定义常见的域名前缀 SUB_DOMAINS = ['www', 'mail', 'ftp', 'vpn', 'admin', 'blog', 'test'] # 定义队列用于存储域名 sub_domains_queue = queue.Queue() # 定义锁,用于线程安全 lock = threading.Lock() # 定义函数,用于检查域名是否存在 def check_sub_domain(sub_domain): try: # 尝试解析域名 ip = socket.gethostbyname(sub_domain + '.' + TARGET_DOMAIN) with lock: # 如果解析成功,则将域名加入队列 sub_domains_queue.put(sub_domain) print('[*] Found sub domain:', sub_domain, ip) except: pass # 定义函数,用于创建并启动线程 def create_and_start_threads(): threads = [] for i in range(THREAD_NUM): t = threading.Thread(target=worker) threads.append(t) t.start() for t in threads: t.join() # 定义函数,用于线程工作 def worker(): while True: try: # 从队列获取域名 sub_domain = sub_domains_queue.get(timeout=1.0) check_sub_domain(sub_domain) except: break # 函数 def main(): # 将常见的域名前缀与目标域名拼接,加入队列 for sub_domain in SUB_DOMAINS: sub_domains_queue.put(sub_domain) # 创建并启动线程 create_and_start_threads() if __name__ == '__main__': main() ``` 以上代码使用了多线程技术,将常见的域名前缀与目标域名拼接后,加入队列。然后创建多个线程,从队列获取域名,尝试解析域名,如果解析成功,则将域名加入队列。最终输出所有解析成功的域名。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值