线程池使用实践

为了尽量减少耗时操作对Action执行的影响,使用TaskExecutor线程池来管理耗时任务,作为后台进程执行,从而解决了问题。

场景:
    使用了Struts和Spring,但Struts的Action并未交给Spring容器管理,Spring容器仅仅用来管理Dao。

要求:
    对每个Action,实现向数据库写入Log功能,最好做到不要影响正常的操作流程 。Log的内容是此Action的请求参数,由客户端决定。

分析:
    如何截获或者使得Action执行前后做写入Log动作并非关键,难点在于“不要影响正常的操作流程”。可以将Action交给Spring容器管理,利用AOP功能将向要执行的动作添加在Action执行前后;或者为所有Action添加一含有写入Log动作的父类。
    不影响正常的操作流程,最理想的状态就是实现导弹发射出去后不管的功能。即在Action执行的某个步骤中,执行一下写入Log的命令,然后继续执行后继任务,写入Log任务让其自己去执行,无需等待返回信息。所以写入Log功能就不能与Action处于同一进程,否则Log任务会抢占资源而让Action的后续任务处于等待状态。
    势必需要启动新的进程来执行Log任务。对于一个Action还不算复杂,多个呢?启动的新进程如何管理就成了新的问题。如果解决了这个问题,原来的问题就显得很容易了。

实现:
    根据以上分析,希望有这么一个进程提供者,有这样的功能:能接受任务;当有可用线程时,任务获得调度执行;无可用资源时,任务处于等待状态;任务执行完成之后,释放进程资源给进程管理者。这就是一个进程池的基本功能。正好Spring定义了-- TaskExecutor线程池(想想Spring还真是无所不有啊!),为各种进程池提供了统一的用户接口。
    这里就不去分析各种不同线程池的功能了。简单地讲一下对上面提出要求的解决步骤:
    1 定义写入Log任务;
        线程池可接受的任务都必须是实现了Runable接口的类,因此定义一个写入Log的任务如下:
       
     class  LogTask  implements  Runnable  {
        
        
private AccessLog accessLog = null;
        
private AccessLogDao accessLogDao = null;
        
        
public LogTask(AccessLog accessLog, AccessLogDao accessLogDao) {
            
this.accessLog = accessLog;
            
this.accessLogDao = accessLogDao;
        }

        
        
public void run() {
            accessLogDao.addLog(accessLog);
        }

    }
其中的AccessLog是一个简单Bean,定义了需要写入的各种信息;AccessDao专门向数据库中写入AccessLog中的内容提供服务。在new一个LogTask的时候,必须提供这两个参数。
    2 实现用TaskExcutor执行写入Log任务;
        首先指定TaskExcutor接口的实现者。将具体的TaskExcutor用定义的方式配置并由Spring容器管理:
     < bean  id ="taskExecutor"
        class
="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" >
        
< property  name ="corePoolSize"  value ="2"   />
        
< property  name ="keepAliveSeconds"  value ="200"   />
        
< property  name ="maxPoolSize"  value ="10"   />
        
< property  name ="queueCapacity"  value ="60"   />
    
</ bean >
可以看到,具体使用的是ThreadPoolTaskExecutor。这样更换别的线程池也比较方便。
        将任务加入到进程池并执行则比较简单:
        LogTask logTask  =   new  LogTask(accessLog, accessLogDao);
        taskExecutor.execute(logTask);
加入任务后,进程池会根据调度算法来执行任务(调度算法也可以自定义)。
    3 定义服务接口,并实现;
       定义一个Action可以方便使用的接口:
public   interface  AccessLogService  {

    
public void writeLog(HttpServletRequest request);
}
它接受的参数就是包含了客户端请求参数的 HttpServletRequest。这样就不需要Action去作参数分析了,具体的工作留给 AccessLogService完成。
        实现此接口:
public   class  AccessLogServiceImpl implements AccessLogService  {
    
    
/*
     * (non-Javadoc)
     * @see my.test.service.AccessLogService#writeLog(javax.servlet.http.HttpServletRequest)
     
*/

    
public void writeLog(HttpServletRequest request) {

        
//分析request,创建AccessLog对象
        AccessLog accessLog = new AccessLog();
        
//以下略

        writeLog(accessLog);        
    }

    
    
    
    
//-------------------------------------------------------------------------
    
    
/**
     * 写入Log信息<br>
     * 
     * @param accessLog
     
*/

    
private void writeLog(AccessLog accessLog) {
        
        LogTask logTask 
= new LogTask(accessLog, accessLogDao);
        taskExecutor.execute(logTask);
    }

    
    
/** DAO */
    
private AccessLogDao accessLogDao = (AccessLogDao) getDaoByName("accessLogDao");
    
    
/** 线程池 */
    
private TaskExecutor taskExecutor = (TaskExecutor) getDaoByName("taskExecutor");
    
    
//-------------------------------------------------------------------------
    
    
/**
     * 任务定义
     *
     
*/

    
class LogTask implements Runnable {
        
        
private AccessLog accessLog = null;
        
private AccessLogDao accessLogDao = null;
        
        
public LogTask(AccessLog accessLog, AccessLogDao accessLogDao) {
            
this.accessLog = accessLog;
            
this.accessLogDao = accessLogDao;
        }

        
        
public void run() {
            accessLogDao.addLog(accessLog);
        }

    }

}
这里的任务被作为内部类定义的。

    4 为Action加入写入Log功能。
        直接使用策略模式,为所有Action添加一父类,在父类中调用写入Log功能,而具体的Action执行流程由子类决定。
public   abstract   class  BaseAction  extends  Action  {
    
    
public ActionForward execute(
            ActionMapping mapping,
            ActionForm actionForm, 
            HttpServletRequest request,
            HttpServletResponse response) 
        
throws Exception {

        
try {
            ActionForward forward 
= _execute(mapping, actionForm, request, response);

        }
 catch (Exception e) {
            
throw e;

        }
 finally {
            AccessLogService als 
= new AccessLogServiceImpl();
            als.writeLog(request);
        }

    }


    
//子类必须实现此方法
    public abstract ActionForward _execute( ActionMapping mapping,
            ActionForm actionForm, 
            HttpServletRequest request,
            HttpServletResponse response) 
throws Exception;

}
子类中只需 实现抽象方法 _execute即可。在执行完子类的 _execute之后,才会将写入Log的任务放入进程池,然后返回。具体任务何时被执行,则由进程池管理。
通过分析执行时log可知,Action返回之后,写入Log任务才被执行。
也就是说,这样做了之后,很大程度上减少了对Action执行流程的干扰,同时还完成了比较耗时的写数据库的工作。

总结:这只是为了解决项目中的某个特殊的需求而做的,可能也不是什么好的通用的办法。但它也给我们以后解决比如需要后台执行之类的要求提供了一种可行的方案。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自定义线程池的最佳实践包括以下几个方面: 1. 合理配置线程池大小:线程池的大小应根据系统资源和任务特性进行配置。如果任务是CPU密集型的,线程数不宜超过CPU核心数;如果任务是IO密集型的,线程数可以适当增加,以充分利用IO资源。可以通过监控系统负载和任务执行情况来调整线程池大小。 2. 设置合适的拒绝策略:当线程池已满且无法处理新的任务时,需要设置合适的拒绝策略。常用的拒绝策略有抛出异常、丢弃任务、丢弃最旧的任务和调用者运行等。选择合适的拒绝策略可以避免系统资源耗尽或任务堆积导致系统崩溃。 3. 使用合适的任务队列:线程池中的任务队列可以缓冲待执行的任务,避免任务提交和任务执行速度不匹配导致的资源浪费。常用的任务队列有无界队列和有界队列。无界队列可以无限制地缓存任务,但可能会导致内存溢出;有界队列则限制了任务的数量,可以避免资源耗尽,但可能会导致任务丢失。 4. 考虑线程池的生命周期管理:线程池的创建、启动、关闭和销毁都需要合理管理。线程池应该在应用程序启动时创建,并在应用程序关闭时进行优雅关闭,以确保所有任务都能得到执行。在线程池不再使用时,应及时销毁,释放资源。 5. 监控和调优:监控线程池的运行状态,包括线程数、任务队列长度、任务执行情况等。根据监控结果,进行适当的调整,例如调整线程池大小、调整拒绝策略或调整任务队列的容量。 以上是自定义线程池的一些最佳实践,根据具体的应用场景和需求,还可以结合使用一些第三方库或框架来简化线程池的创建和管理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值