Spring定时任务中使用ThreadLocal的坑
在项目中,发现Spring的定时任务中用ThreadLocal来保存上下文信息,且上下文信息中有一个属性在后面入库时是做为主键值。总觉得这里应该有问题,因为没有去看过Spring的定时任务的具体实现,也不知定时任务用没用线程池以及如何使用的,但如何用了线程池(且我觉得从性能和常理推测来看应该是要用的),用ThreadLocal保存上下文信息,并在后续使用(且使用完没有做remove),那么后面就一定会出现主键冲突呀。
为了偷懒,也为了尽快验证我的推断,写了一个很简单的测试代码如下。
为了偷懒,也为了尽快验证我的推断,写了一个很简单的测试代码如下。
Context.java
package com.bijian.study.dto;
public class Context {
private int seqNo;
public int getSeqNo() {
return seqNo;
}
public void setSeqNo(int seqNo) {
this.seqNo = seqNo;
}
}
HelloService.java
package com.bijian.study.service;
public interface HelloService {
public String processService(String name) throws Exception;
public String processService() throws Exception;
}
HelloServiceImpl.java
package com.bijian.study.service.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.bijian.study.dto.Context;
import com.bijian.study.service.HelloService;
import com.bijian.study.util.ContextThreadLocal;
@Service("helloService")
public class HelloServiceImpl implements HelloService {
private static Logger logger = LoggerFactory.getLogger(HelloServiceImpl.class);
private static int count;
@Override
public String processService(String name) throws Exception {
logger.info("HelloService processService name:" + name);
return "Hello " + name;
}
@Override
public String processService() throws Exception {
Context context = ContextThreadLocal.get();
if(context == null) {
count++;
context = new Context();
context.setSeqNo(count);
ContextThreadLocal.set(context);
}
logger.info("HelloService processService seqNo:" + context.getSeqNo());
return "Hello " + context.getSeqNo();
}
}
ContextThreadLocal.java
package com.bijian.study.util;
import com.bijian.study.dto.Context;
public class ContextThreadLocal {
public static final ThreadLocal<Context> userContextThreadLocal = new ThreadLocal<Context>();
public static void set(Context userContext) {
userContextThreadLocal.set(userContext);
}
public static void unset() {
userContextThreadLocal.remove();
}
public static Context get() {
return userContextThreadLocal.get();
}
}
springMVC-servlet.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd"> <context:component-scan base-package="com.bijian.study"></context:component-scan> <context:annotation-config /> <mvc:annotation-driven></mvc:annotation-driven> <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/views" /> <property name="suffix" value=".jsp" /> </bean> <task:scheduled-tasks> <task:scheduled ref="helloService" method="processService" initial-delay="5000" fixed-delay="1"/> </task:scheduled-tasks> </beans>
运行结果:
从运行结果来看,Spring定时任务肯定用到了线程,且在上面这种运用场景是肯定会出现主键冲突。当然,这里的解决办法也很简单,即在使用完ThreadLocal中的上下文信息后,做remove的清理动作。