spring异步线程传递线程上下文

我们直接使用@Async注解,当然你也可以直接使用线程池,效果是一样的
首先我们创建ContextDecorator实现TaskDecorator接口

package com.qimo.omsa.demo.thread;

import java.util.Map;
import org.springframework.core.task.TaskDecorator;

/**
 * @Description TODO
 * @Author 姚仲杰#80998699
 * @Date 2022/1/30 13:55
 */
public class ContextDecorator implements TaskDecorator {
    
    @Override
    public Runnable decorate(Runnable runnable) {
        return new ContextDecorator.CopyContextToSubThread(runnable);
    }
    
    public static class CopyContextToSubThread implements Runnable{
        private Runnable runnable;
        private Map<String, String> contextMap;
        
        CopyContextToSubThread(Runnable runnable){
            this.runnable=runnable;
            this.contextMap=SystemContext.getContextMap();
        }
        @Override
        public void run() {
            try{
                if (contextMap != null) {
                    SystemContext.setContextMap(contextMap);
                }
                this.runnable.run();
            }finally {
                SystemContext.clean();
            }
        }
    }
}

接着我们配置线程池

package com.qimo.omsa.demo.thread;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * @Description TODO
 * @Author 姚仲杰#80998699
 * @Date 2022/3/7 17:59
 */
@Configuration
@EnableAsync()
public class DecoratorThreadPool {
    @Bean
    ThreadPoolTaskExecutor threadPoolTaskExecutor(){
        ThreadPoolTaskExecutor threadPoolTaskExecutor=new ThreadPoolTaskExecutor();
        //核心线程数
        threadPoolTaskExecutor.setCorePoolSize(10);
        //最大线程数
        threadPoolTaskExecutor.setMaxPoolSize(10);
        //设置线程装饰器
        threadPoolTaskExecutor.setTaskDecorator(new ContextDecorator());
        //设置阻塞队列容量
        threadPoolTaskExecutor.setQueueCapacity(500);
        //设置线程前缀
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
    
}

写个ThreadLocal,一般线程上下文我们要稍微控制下大小。

package com.qimo.omsa.demo.thread;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description TODO
 * @Author 姚仲杰#80998699
 * @Date 2022/3/7 17:37
 */
public class SystemContext {
    private static transient ThreadLocal<Map<String,String>> contextMap=new ThreadLocal<>();
    private static Integer MAX_CAPACITY = 200;
    private static Integer MAX_SIZE = Integer.MAX_VALUE;
    
    public static String get(String key) {
        Map<String, String> contextMap = getContextMap();
        return contextMap == null ? null : contextMap.get(key);
    }
    
    public static String put(String key, String value) {
        if (key != null && value != null) {
            if (key.length() > MAX_SIZE) {
                throw new RuntimeException("key is more than " + MAX_SIZE + ", i can't put it into the context map");
            } else if (value.length() > MAX_SIZE) {
                throw new RuntimeException("value is more than " + MAX_SIZE + ", i can't put it into the context map");
            } else {
                Map<String, String> contextMap = getContextMap();
                if (contextMap == null) {
                    contextMap = new HashMap();
                    setContextMap((Map)contextMap);
                }
                
                if (((Map)contextMap).size() > MAX_CAPACITY) {
                    throw new RuntimeException("the context map is full, can't put anything");
                } else {
                    return (String)((Map)contextMap).put(key, value);
                }
            }
        } else {
            throw new RuntimeException("key:" + key + " or value:" + value + " is null,i can't put it into the context map");
        }
    }
    
    
    public static Map<String, String> getContextMap() {
        return (Map)contextMap.get();
    }
    
    public static void setContextMap(Map<String, String> contextMap) {
        SystemContext.contextMap.set(contextMap);
    }
    
    public static void clean() {
        contextMap.remove();
    }
}

接着我们再写个测试类controllerservice

package com.qimo.omsa.demo.thread;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description TODO
 * @Author 姚仲杰#80998699
 * @Date 2022/3/7 18:14
 */
@RestController
public class ThreadControllerTest {
    Logger logger= LoggerFactory.getLogger(ThreadControllerTest.class);
    
    @Autowired
    ThreadServiceTest threadServiceTest;
    
    @GetMapping("/thread/test")
    public String testThread(){
        SystemContext.put("xxxxx","xxxxxxx");
        logger.info("aaaaaaaa");
        threadServiceTest.subThread();
        return "success";
    }
}

service

package com.qimo.omsa.demo.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @Description TODO
 * @Athor 姚仲杰#80998699
 * @Date 2022/3/7 18:24
 */
@Service
public class ThreadServiceTest {
    Logger logger= LoggerFactory.getLogger(ThreadControllerTest.class);
    @Async
    public void subThread(){
        logger.info(SystemContext.get("xxxxx"));
    }
}

然后启动项目,请求接口你会发现如下日志

#这个是request的线程日志,线程名字为http-nio-8080-exec-1
2022-03-07 19:08:30.768 +0800 [TID: N/A] [http-nio-8080-exec-1] INFO  com.qimo.omsa.demo.thread.ThreadControllerTest:26 aaaaaaaa
#而这个是异步线程的日志即subThread方法打印的
2022-03-07 19:08:39.015 +0800 [TID: N/A] [threadPoolTaskExecutor-1] INFO  com.qimo.omsa.demo.thread.ThreadControllerTest:19 xxxxxxx

你会发现你在http-nio-8080-exec-1中放进去的xxxxxxx,在threadPoolTaskExecutor-1子线程中能取到了。

原理其实很简单,就是使用了一个装饰器模式装饰了下线程。
我们来查看ThreadPoolTaskExecutor中的源码

@Override
	protected ExecutorService initializeExecutor(
			ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {

		BlockingQueue<Runnable> queue = createQueue(this.queueCapacity);

		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);
				}
			};
		}
		else {
			executor = new ThreadPoolExecutor(
					this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
					queue, threadFactory, rejectedExecutionHandler);

		}

		if (this.allowCoreThreadTimeOut) {
			executor.allowCoreThreadTimeOut(true);
		}

		this.threadPoolExecutor = executor;
		return executor;
	}

此方法复写了ThreadPoolExecutorexecute 方法,实际上也就对Runnable进行了一个装饰,并且把怎么装饰的扩展留给了程序员去实现,所以我们实现了TaskDecorator接口之后并把他配置给ThreadPoolTaskExecutor,它就会根据我们自定义的逻辑进行装饰了。

public class ContextDecorator implements TaskDecorator {
    //装饰器我们接收一个Runnable,并且在它之前做一些什么事情,例如把父线程的SystemContext传递过来,然后在run里面再把contextMap给到当前线程。然后再执行实际的this.runnable.run();
    @Override
    public Runnable decorate(Runnable runnable) {
        return new ContextDecorator.CopyContextToSubThread(runnable);
    }
    
    public static class CopyContextToSubThread implements Runnable{
        private Runnable runnable;
        private Map<String, String> contextMap;
        
        CopyContextToSubThread(Runnable runnable){
            this.runnable=runnable;
            this.contextMap=SystemContext.getContextMap();
        }
        @Override
        public void run() {
            try{
                if (contextMap != null) {
                    SystemContext.setContextMap(contextMap);
                }
                this.runnable.run();
            }finally {
                SystemContext.clean();
            }
        }
    }
}

我们知道runnable只是一个接口,需要将runnable装给Thread,最终等thread.start()方法被调用时,由native的start0() 方法回调run()方法来执行,所以,以上就是把原来的run方法当作一个普通方法进行装饰。而真正线程的方法为我们装饰器的run方法,spring就是巧妙的利用了这一点。当然我们在使用完ThreadLocal之后记得回收避免数据污染和内存泄漏。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的示例: 假设我们有一个 UserService,其中有一个方法 sendEmail,它需要异步地发送电子邮件。现在我们想要将当前用户的信息透传给异步任务中使用的线程。 首先,我们需要在异步方法上添加 @Async 注解,并在配置类中启用异步支持: ```java @Configuration @EnableAsync public class AppConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(100); executor.setQueueCapacity(10); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new CustomAsyncExceptionHandler(); } } ``` 在上面的示例中,我们创建了一个 ThreadPoolTaskExecutor,它将用于执行异步任务。我们还实现了 AsyncConfigurer 接口,并覆盖了 getAsyncExecutor 和 getAsyncUncaughtExceptionHandler 方法,以提供自定义的 Executor 和异常处理程序。 现在我们需要将当前用户信息存储在一个 ThreadLocal 对象中。这可以通过一个拦截器来实现: ```java public class UserContextInterceptor extends HandlerInterceptorAdapter { private final ThreadLocal<String> userThreadLocal = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String currentUser = request.getHeader("X-User"); userThreadLocal.set(currentUser); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { userThreadLocal.remove(); } public String getCurrentUser() { return userThreadLocal.get(); } } ``` 在上面的示例中,我们创建了一个 UserContextInterceptor,它将在每个请求的开始和结束时执行。在 preHandle 方法中,我们从请求头中获取当前用户信息,并将其存储在一个 ThreadLocal 对象中。在 afterCompletion 方法中,我们将删除该信息,以避免内存泄漏。 现在,我们可以在 UserService 的 sendEmail 方法中使用 UserContextInterceptor 中存储的当前用户信息: ```java @Service public class UserService { @Autowired private JavaMailSender mailSender; @Autowired private UserContextInterceptor userContextInterceptor; @Async public void sendEmail(String to, String subject, String text) { String currentUser = userContextInterceptor.getCurrentUser(); // 使用当前用户信息发送电子邮件 // ... } } ``` 在上面的示例中,我们使用 @Autowired 注解将 UserContextInterceptor 注入到 UserService 中。在 sendEmail 方法中,我们从 UserContextInterceptor 中获取当前用户信息,并在发送电子邮件时使用它。 通过这种方式,我们可以将当前用户信息透传给异步任务中使用的线程

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值