EJB @Schedule等到方法完成

时间:2013-01-18 15:26:59

标签: java-ee ejb-3.1 schedule job-scheduling

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

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

工作正常。

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

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

4 个答案:

答案 0 :(得分:63)

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

首先@Timer应该出现在@Singleton上。在Singleton方法中,默认情况下是写锁定的,因此当尝试调用timer方法时,容器将自动被锁定,而其中仍有活动。

以下基本上就足够了:

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

}

工作bean:

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

}

工作bean:

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

}

可能存在更多变化,例如你可以将繁忙的检查委托给一个拦截器,或者将一个只包含boolean的单例注入到计时器bean中,并在那里检查那个boolean等。

答案 1 :(得分: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();
        }
    }

}

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

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

答案 2 :(得分:5)

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

在例如@Singleton @Startup @LocalBean中,注入默认的" managed-scheduled-executor-service"在standalone.xml中配置:

@Resource
private ManagedScheduledExecutorService scheduledExecutorService;

安排@PostConstruct中的某项任务执行,即每隔一秒固定延迟

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

scheduleWithFixedDelay

  

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

请勿在{{1​​}}:

中关闭调度程序
  

托管预定执行程序服务实例由   应用程序服务器,因此禁止Java EE应用程序调用   任何与生命周期相关的方法。

答案 3 :(得分:0)

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

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

}