42

Can anyone advise here? I have a situation where users will submit data mining requests interactively via a Java JSP and servlet to an application of mine which will dynamically work out association rules on data and more.

As such a job may take a while, I'm thinking of some sort of process on the server to run such a request in the background so it dosen't 'lock' the session and possibly use massive amounts of server memory to the detriment of the system.

As the system is made up of a series of Java JSPs and servlets running in a Tomcat container over a MySQL database, can anyone advise a way forward?

Thanks

Mr Morgan

mr morgan
  • 451
  • 1
  • 6
  • 5
  • Are you expecting to give a response back? Also, Tomcat use threadpools which can expand when necessary to serve multiple simultaneous requests. – Johan Sjöberg Feb 05 '11 at 14:59
  • Do you need to display the results of the "data mining requests" back to the users (or at least notify them of completed jobs) ? And how do you need to do that ? – Costi Ciudatu Feb 05 '11 at 15:02
  • I was thinking that when a job is finished, the user can be emailed to say that results are available. – mr morgan Feb 05 '11 at 15:40

4 Answers4

49

Use an ExecutorService.

There's a few things you should do though, mark the thread as a daemon thread so it atleast won't tie up tomcat in error scenarios, and you should stop the executor when your servlet context is destroyed (e.g. when you redeploy or stop your application. To do this, use a 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;
    }
}

You'll have to add the context listener to your web.xml, where you also can specify the number of threads you'll want to run the background jobs:

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

You can access the executor from your servlet and submit jobs to it:

ExecutorService executor = (ExecutorService )getServletContext().getAttribute("MY_EXECUTOR");
...
executor.submit(myJob);

If you're using Spring, all this can probably be made even simpler

nos
  • 207,058
  • 53
  • 381
  • 474
  • How would a non-daemon thread tie up tomcat in an error scenario? – David Mann Sep 07 '12 at 20:35
  • I was going down this road, but I am concerned about being able to dependably and gracefully shutdown my ExecutorService when tomcat is shutting down. I can't lose what is executing. Apparently you can't depend on ServletContextListener. See http://stackoverflow.com/questions/1549924/shutdown-hook-for-java-web-application and http://stackoverflow.com/questions/11443133/shutdown-hook-in-tomcat-necessary-not-running – broc.seib Feb 26 '13 at 18:53
  • @nos, then the myJob Runnable should have a run method like `while(true){Thread.sleep(1000); // do something more..}` .isn't it ?] – Débora Sep 04 '14 at 10:40
  • 1
    @Débora I don't know what you want to do in your run() method. You'd almost never have an infinite loop in a job you'd use with an Executor though. – nos Sep 04 '14 at 12:58
  • @nos. Thanks for reply. What I mean was, assume that if the program (Something like, application statistics checking... ?) should be working continuously/parallel to the tomcat running, then won't we have to run the deamon thread infinitely ? – Débora Sep 05 '14 at 05:24
  • @broc.seib Yes, you would create a class implementing `ServletContextListener` interface to both spin up and shutdown your ExecutorService. The executor has methods for either gracefully shut down your executing threads ([`shutdown`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown--)) or try a more rude interruption ([`shutdownNow`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdownNow--)). I've seen some shutdown problems in development (NetBeans driving Tomcat with JRebel in the mix) but not in production. – Basil Bourque May 21 '15 at 01:01
  • Hello @nos ! I followed all your steps but when I launch my process through the executorService, the process starts, then I do a redirect to my main page but then it gets stuck there. I cannot send new http requests, do you know why? Thank you by advance for your help. Cheers, Jeremy. – Jeremy.S Apr 01 '16 at 15:55
5

I think Quartz scheduler should be able to accomplish what you want to do here. Here are some of the examples from Quartz. Using this, you can start a cron that rapidly polls to process the incoming request(s). I actually did that for one of my projects.

limc
  • 36,786
  • 19
  • 95
  • 142
  • I use Quartz elsewhere in the system for hourly scheduled jobs but it is a possibility. – mr morgan Feb 05 '11 at 15:42
  • 3
    Quartz is a good call. It will do "Run job once, now" quite well. The other advantage is that it will give you restart capability. When you start the job, quartz can "save" the request, and start the job up again should the system go down. You get all that "for free", and since you already have it, you already know how to use it. Why add new kit to you stack if you don't have to? – Will Hartung Feb 05 '11 at 16:28
4

A lightweight solution (as opposed to using a scheduler like quartz) would be to put the processing into a background thread and run that through an ExecutorService.

http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Executors.html
http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ExecutorService.html

a_horse_with_no_name
  • 440,273
  • 77
  • 685
  • 758
0

Maybe JMS is what you really looking for however it requires extra effort to use that on Tomcat because it is only Servlet Container. You may eigher switch to real AppServer like Glassfish or JBoss or add JMS functionality to Tomcat by your own (below link)

http://blogs.captechconsulting.com/blog/jairo-vazquez/tomcat-and-jms

Marcin Michalski
  • 1,291
  • 12
  • 17