37

我想编写一个每分钟执行一次的后台作业(EJB 3.1)。为此,我使用以下注释:

@Schedule(minute = "*/1", hour = "*")

这工作正常。

但是,有时这项工作可能需要超过一分钟。在这种情况下,定时器仍然被触发,导致线程问题。

如果当前执行未完成,是否有可能终止调度程序?

4

4 回答 4

69

如果只有 1 个计时器可能同时处于活动状态,则有两种解决方案。

首先,@Timer应该可能出现在@Singleton. 在单例中,方法默认是写锁定的,因此当容器中仍有活动时尝试调用计时器方法时,容器将自动被锁定。

以下基本就够了:

@Singleton
public class TimerBean {

    @Schedule(second= "*/5", minute = "*", hour = "*", persistent = false)
    public void atSchedule() throws InterruptedException {

        System.out.println("Called");
        Thread.sleep(10000);
    }
}

atSchedule默认情况下是写锁定的,其中只能有一个线程处于活动状态,包括由容器发起的调用。

在被锁定后,容器可能会重试计时器,因此为了防止这种情况,您将使用读锁代替并委托给第二个 bean(需要第二个 bean,因为 EJB 3.1 不允许将读锁升级到写锁)。

计时器 bean:

@Singleton
public class TimerBean {

    @EJB
    private WorkerBean workerBean;

    @Lock(READ)
    @Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
    public void atSchedule() {

        try {
            workerBean.doTimerWork();
        } catch (Exception e) {
            System.out.println("Timer still busy");
        }
    }

}

工人豆:

@Singleton
public class WorkerBean {

    @AccessTimeout(0)
    public void doTimerWork() throws InterruptedException {
        System.out.println("Timer work started");
        Thread.sleep(12000);
        System.out.println("Timer work done");
    }
}

这可能仍会在日志中打印嘈杂的异常,因此更详细但更安静的解决方案是使用显式布尔值:

计时器 bean:

@Singleton
public class TimerBean {

    @EJB
    private WorkerBean workerBean;

    @Lock(READ)
    @Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
    public void atSchedule() {
        workerBean.doTimerWork();
    }

}

工人豆:

@Singleton
public class WorkerBean {

    private AtomicBoolean busy = new AtomicBoolean(false);

    @Lock(READ)
    public void doTimerWork() throws InterruptedException {

        if (!busy.compareAndSet(false, true)) {
            return;
        }

        try {
            System.out.println("Timer work started");
            Thread.sleep(12000);
            System.out.println("Timer work done");
        } finally {
            busy.set(false);
        }
    }

}

还有一些可能的变体,例如,您可以将繁忙检查委托给拦截器,或者将仅包含布尔值的单例注入计时器 bean,并在那里检查该布尔值等。

于 2013-01-19T12:44:50.737 回答
7

我遇到了同样的问题,但解决方法略有不同。

@Singleton
public class DoStuffTask {

    @Resource
    private TimerService timerSvc;

    @Timeout
    public void doStuff(Timer t) {
        try {
            doActualStuff(t);
        } catch (Exception e) {
            LOG.warn("Error running task", e);
        }
        scheduleStuff();
    }

    private void doActualStuff(Timer t) {

        LOG.info("Doing Stuff " + t.getInfo());
    }

    @PostConstruct
    public void initialise() {
        scheduleStuff();
    }

    private void scheduleStuff() {
        timerSvc.createSingleActionTimer(1000l, new TimerConfig());
    }

    public void stop() {
        for(Timer timer : timerSvc.getTimers()) {
            timer.cancel();
        }
    }

}

这通过设置一个任务在将来执行(在这种情况下,在一秒钟内)来工作。在任务结束时,它会再次安排任务。

编辑:更新以将“东西”重构为另一种方法,以便我们可以防范异常,以便始终重新安排计时器

于 2013-12-06T15:26:35.437 回答
6

从 Java EE 7 开始,可以使用“EE-aware” ManagedScheduledExecutorService,即在 WildFly 中:

例如在 a@Singleton @Startup @LocalBean中,注入在以下配置的默认“managed-scheduled-executor-service” standalone.xml

@Resource
private ManagedScheduledExecutorService scheduledExecutorService;

安排一些@PostConstruct要执行的任务,即每秒以固定延迟执行:

scheduledExecutorService.scheduleWithFixedDelay(this::someMethod, 1, 1, TimeUnit.SECONDS);

scheduleWithFixedDelay

创建并执行一个周期性操作,该操作首先在给定的初始延迟之后启用,随后在一个执行终止和下一个执行开始之间具有给定的延迟。[...]

不要在 ie 中关闭调度程序@PreDestroy

Managed Scheduled Executor Service 实例由应用服务器管理,因此 Java EE 应用程序被禁止调用任何与生命周期相关的方法。

于 2016-10-07T10:09:45.080 回答
1

好吧,我有一个类似的问题。有一个作业应该每 30 分钟运行一次,有时该作业需要 30 多分钟才能完成,在这种情况下,另一个作业实例正在启动,而前一个作业尚未完成。我通过使用一个静态布尔变量来解决它,我的工作将在它开始运行时将其设置为 true,然后在完成时将其设置回 false。由于它是一个静态变量,所有实例将始终看到相同的副本。当您设置和取消设置静态变量时,您甚至可以同步块。类 myjob{ 私有静态布尔 isRunning=false;

public executeJob(){
if (isRunning)
    return;
isRunning=true;
//execute job
isRunning=false;
  }

}
于 2015-03-12T17:00:36.403 回答