如何按固定间隔运行后台作业方法?

7

我正在使用Apache Tomcat上的JSP/Servlet技术。我需要每10分钟运行一次一个方法。请问如何实现?

2个回答

11

由于您正在使用Tomcat,它只是一个基本的servlet容器,因此您无法使用Java EE规范推荐的EJB的@Schedule。您最好使用Java 1.5的java.util.concurrent包中的ScheduledExecutorService。您可以通过ServletContextListener触发它,如下所示:

@WebListener
public class BackgroundJobManager implements ServletContextListener {

    private ScheduledExecutorService scheduler;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(new SomeTask(), 0, 10, TimeUnit.MINUTES);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        scheduler.shutdownNow();
    }

}

其中SomeTask类看起来像这样:

public class SomeTask implements Runnable {

    @Override
    public void run() {
        // Do your job here.
    }

}

如果您实际上在使用一个带有EJB支持的真正Java EE容器(如Glassfish、JBoss AS、TomEE等),那么您可以使用@Singleton EJB和@Schedule方法。这样,容器将自动处理线程池和销毁线程。您所需要的就是以下EJB:
@Singleton
public class SomeTask {

    @Schedule(hour="*", minute="*/10", second="0", persistent=false)
    public void run() {
        // Do your job here.
    }

} 

请注意,使用此方法,您可以继续以通常的方式(例如@PersistenceContext等)透明地使用容器管理的事务,但是这在ScheduledExecutorService中是不可能的 - 您必须手动获取实体管理器并手动启动/提交/结束事务,但是在像Tomcat这样的基本servlet容器上,您默认情况下已经没有其他选择。
请注意,在假定为“终身长久”运行的Java EE Web应用程序中永远不应使用 Timer 。 它具有以下主要问题,使其不适用于Java EE(引用自Java Concurrency in Practice):
  • Timer 对系统时钟的变化比较敏感,ScheduledExecutorService 则不是。
  • Timer 只有一个执行线程,因此长时间运行的任务可能会延迟其他任务。 ScheduledExecutorService 可以配置任意数量的线程。
  • TimerTask 中抛出的任何运行时异常都会终止该线程,因此使 Timer 失效,即预定的任务将不再运行(直到重新启动服务器)。 ScheduledThreadExecutor 不仅捕获运行时异常,而且如果需要还可以让您处理它们。引发异常的任务将被取消,但其他任务将继续运行。

非常好的答案,谢谢@BalusC。不过我有一个问题:如果我需要更改调度程序之间的时间间隔,该怎么修改呢? 例如,我有一些“int frequency”,用户可以在WebGUI中设置它,表示以秒为单位运行之间的时间。 - Gewure

4

请阅读ScheduledExecutorService,它必须由ServletContextListener初始化。

public class MyContext implements ServletContextListener 
{
    private ScheduledExecutorService sched;

    @Override
    public void contextInitialized(ServletContextEvent event) 
    {
        sched = Executors.newSingleThreadScheduledExecutor();
        sched.scheduleAtFixedRate(new MyTask(), 0, 10, TimeUnit.MINUTES);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) 
    {
        sched.shutdownNow();
    }
}

此外,你可以尝试使用Java 计时器(Timer)并从ServletContextListener(Servlet上下文监听器)中使用,但在Java EE容器中不建议这样做,因为它会夺取控制线程资源的权限,最好的方式是使用ScheduledExecutorService(第一种选项)。
Timer timer = new Timer("MyTimer");
MyTask t = new MyTask();

//Second Parameter is the specified the Starting Time for your timer in
//MilliSeconds or Date

//Third Parameter is the specified the Period between consecutive
//calling for the method.

timer.schedule(t, 0, 1000*60*10);

实现TimerTaskMyTask是一个实现Runnable接口的类,所以您需要使用您的代码重写run方法:

class MyTask extends TimerTask 
{
  public void run() 
  { 
    // your code here
  }
}

好的,我明白了。但是我需要从哪里调用这个方法呢?我需要在我的应用程序启动后立即开始它。 - Nirbhay Mishra
2
永远不要在Java EE中使用Timer - BalusC
感谢@BalusC,我已经在我的回答中明确提到了这一点。 - user1697575

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