`

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的清理动作。

  至于Spring Task的实现原理,还请看过这块代码或了解这块的读者指点一下,或者,有详细分析Spring Task实现原理的文章,也可以推荐一下,多谢。
  • 大小: 92.2 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics