EJB @Schedule等到方法完成

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

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

这很好用。

但是,有时作业可能需要一分钟以上。在这种情况下,计时器仍会触发,从而导致线程问题。

如果当前执行未完成,是否可以以某种方式终止调度程序?


答案 1

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

首先,可能应该出现在 .在 Singleton 中,方法默认为写锁定,因此在尝试调用计时器方法时,容器将自动锁定,而其中仍有活动。@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不允许将读锁定升级到写锁定)。

计时器豆:

@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");
    }
}

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

计时器豆:

@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,并在那里检查布尔值等。


答案 2

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

@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();
        }
    }

}

这可以通过设置将来要执行的任务(在本例中为一秒钟)来工作。在任务结束时,它会再次计划任务。

编辑:更新以将“东西”重构为另一种方法,以便我们可以保护异常,以便计时器的重新调度始终发生


推荐