在Tomcat中运行后台Java程序

43

有人能给些建议吗?我有这样一种情况,用户将通过Java JSP和servlet交互式提交数据挖掘请求到我的应用程序中,该应用程序将动态地处理数据关联规则等内容。

由于这样的任务可能需要一些时间,我正在考虑在服务器上运行一些进程来在后台运行此类请求,以避免“锁定”会话并可能对系统造成大量内存使用的不利影响。

由于该系统是由一系列运行在Tomcat容器中的Java JSP和servlet组成的,并且使用MySQL数据库,有人可以提出解决方案吗?

谢谢

Morgan先生


你期望回复吗?此外,Tomcat使用线程池,可以在需要时扩展以服务多个同时请求。 - Johan Sjöberg
您是否需要向用户显示“数据挖掘请求”的结果(或至少通知他们已完成的作业)?那么您需要如何做到这一点? - Costi Ciudatu
我在考虑,当一个任务完成时,可以通过电子邮件通知用户结果已经可用。 - mr morgan
4个回答

49

使用ExecutorService

但是,有几件事情需要注意,将线程标记为守护线程,这样至少在错误场景下不会阻塞Tomcat,并且在Servlet上下文被销毁时(例如重新部署或停止应用程序时)应该停止执行器。为此,请使用ServletContextListener:

public class ExecutorContextListener implements ServletContextListener {
    private  ExecutorService executor;

    public void contextInitialized(ServletContextEvent arg0) {
        ServletContext context = arg0.getServletContext();
        int nr_executors = 1;
        ThreadFactory daemonFactory = new DaemonThreadFactory();
        try {
            nr_executors = Integer.parseInt(context.getInitParameter("nr-executors"));
        } catch (NumberFormatException ignore ) {}

        if(nr_executors <= 1) {
        executor = Executors.newSingleThreadExecutor(daemonFactory);
        } else {
        executor = Executors.newFixedThreadPool(nr_executors,daemonFactory);
       }
          context.setAttribute("MY_EXECUTOR", executor);
      }

    public void contextDestroyed(ServletContextEvent arg0) {
        ServletContext context = arg0.getServletContext();
        executor.shutdownNow(); // or process/wait until all pending jobs are done
    }

}
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * Hands out threads from the wrapped threadfactory with setDeamon(true), so the
 * threads won't keep the JVM alive when it should otherwise exit.
 */
public class DaemonThreadFactory implements ThreadFactory {

    private final ThreadFactory factory;

    /**
     * Construct a ThreadFactory with setDeamon(true) using
     * Executors.defaultThreadFactory()
     */
    public DaemonThreadFactory() {
        this(Executors.defaultThreadFactory());
    }

    /**
     * Construct a ThreadFactory with setDeamon(true) wrapping the given factory
     * 
     * @param thread
     *            factory to wrap
     */
    public DaemonThreadFactory(ThreadFactory factory) {
        if (factory == null)
            throw new NullPointerException("factory cannot be null");
        this.factory = factory;
    }

    public Thread newThread(Runnable r) {
        final Thread t = factory.newThread(r);
        t.setDaemon(true);
        return t;
    }
}

你需要将上下文监听器添加到web.xml文件中,你还可以在此处指定要运行后台作业的线程数:

  <listener>
    <listener-class>com.example.ExecutorContextListener</listener-class>
  </listener>

您可以从servlet中访问执行器并向其提交作业:
ExecutorService executor = (ExecutorService )getServletContext().getAttribute("MY_EXECUTOR");
...
executor.submit(myJob);

如果您正在使用Spring,所有这些可能都可以变得更加简单。 这里 有更多相关信息。

如果@nos,那么myJob Runnable应该有一个类似于while(true){Thread.sleep(1000); // do something more..}的run方法,对吧? - Débora
1
@ Débora 我不知道你想在run()方法中做什么。但是,你几乎永远不会在使用Executor的作业中运行无限循环。 - nos
@nos。感谢回复。我的意思是,假设程序(类似于应用统计检查...?)应该连续/并行运行到tomcat运行,那么我们不是必须无限地运行守护线程吗? - Débora
@broc.seib 是的,您可以创建一个实现ServletContextListener接口的类来启动和关闭您的ExecutorService。该执行程序有方法可以优雅地关闭您的执行线程(shutdown),或者尝试更粗暴的中断(shutdownNow)。我在开发过程中遇到过一些关闭问题(NetBeans混合使用Tomcat和JRebel),但在生产环境中没有遇到过。 - Basil Bourque
你好 @nos!我按照你的所有步骤进行了操作,但是当我通过executorService启动我的进程时,进程开始运行,然后我重定向到我的主页,但是它卡在那里。我无法发送新的http请求,你知道为什么吗?提前感谢你的帮助。祝好,Jeremy。 - Jeremy.S
显示剩余2条评论

5
我认为Quartz调度程序应该能够完成你想要做的事情。以下是来自Quartz的一些示例链接:Quartz示例。使用这个工具,你可以启动一个cron定时任务,快速轮询以处理传入的请求。我曾经在一个项目中使用过这种方法。

我在系统的其他地方使用Quartz进行每小时定时作业,但这也是一种可能性。 - mr morgan
3
石英是个不错的选择。它可以很好地执行“现在运行一次作业”的任务。另一个优点是它提供了重启功能。当您启动作业时,石英可以“保存”请求,并在系统崩溃后再次启动作业。您可以免费获得所有这些功能,而且既然您已经拥有它,那么您已经知道如何使用它了。如果不必添加新的工具包,为什么要这样做呢? - Will Hartung

4

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接