Spring boot中使用tomcat多线程总结之controller

说明

本系列内容是我自己对于自己做项目过程中的问题的记录,所以希望各位看官看到有误的地方帮忙指正。本系列最主要是想说明一下我在使用tomcat工作线程池executor中的多个线程做一些事情的时候遇到的一些问题。

对于controller中的注意事项

在controller类中,我经常使用@Autowired注解来注入我需要用到的service类对象,一开始我以为只要在service类上使用@Scope(“prototype”)注解就可以保证每一个线程使用上这个service的不同的实例了,就例如这样。

//controller类

@Controller
@RequestMapping("/pipeline")
public class pipelineController {
    @Autowired
    private ProcessService processService;

    @RequestMapping("exec")
    @ResponseBody
    public String executor(String key){
        String execResult = processService.exeProcess(key);
        return execResult;
    }
}

//service类

@Service
@Scope("prototype")
public class ProcessService {
    private String correlationDate = "";

    public String exeProcess(String key){
    	correlationDate = correlationDate+key;
    	return correlationDate;
    }
}

最后的结果显示,我还是太年轻,这样用会导致correlationDate值混乱。后来才想起来,@Scope(“prototype”)是可以保证每次注入都是一个新的实例对象,但是,我的controller实例是只有一个的,也就是说这里service只在创建controller实例的时候注入了一次,多个线程在使用controller的时候,并没有再去创建新的controller实例,而是就使用的springmvc容器里面的controller实例,所以就导致并没有去注入新的service实例,所以最后就导致多个线程同用一个service,导致correlationDate值混乱。

另外这里还提醒了我一点就是对于形参和方法内的局部变量,不需要为多线程安全问题去考虑会不会共用,因为这些变量都是存储在栈帧中的,栈帧是存放在线程的线程栈中的,线程栈是每个线程私有的,所以不存在多线程安全问题。

解决方案

为了解决这个@Autowird只注入一次的问题,这里可以在每次使用的时候才从springmvc容器中获取service实例。

//controller类

@Controller
@RequestMapping("/pipeline")
public class pipelineController {
	@Autowired
	private ApplicationContext applicationContext;

    @RequestMapping("exec")
    @ResponseBody
    public String executor(String key){
    	ProcessService processService = applicationContext.getBean(ProcessService.class);
        String execResult = processService.exeProcess(key);
        return execResult;
    }
}

这样既可解决controller中service在多线程中也只有一个实例的问题;

更高效的解决方案

如上每次都重新获取service实例是可以解决问题的,但是这引入另外一个问题,频繁的创建,销毁service对象增加了程序的负载,同时这减慢了请求的处理速度。为了实现每个线程单独使用自己的service实例,并且不要频繁的创建销毁service对象,可以使用ThreadLocal。具体实现如下:

//controller类

@Controller
@RequestMapping("/pipeline")
public class pipelineController {
	public ThreadLocal<ProcessService> processServiceInstance = new ThreadLocal<>();
	@Autowired
	private ApplicationContext applicationContext;

    @RequestMapping("exec")
    @ResponseBody
    public String executor(String key){
    	ProcessService processService = processServiceInstance.get();
    	if(processService == null){
    		processService = applicationContext.getBean(ProcessService.class);
    		processServiceInstance.set(processService)
    	}
        String execResult = processService.exeProcess(key);
        return execResult;
    }
}