【问题标题】:EJB @Schedule wait until method completedEJB @Schedule 等到方法完成
【发布时间】:2013-01-18 15:26:59
【问题描述】:

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

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

工作正常。

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

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

【问题讨论】:

  • 您在遇到这种行为时使用的是什么应用程序服务器?我使用的是 Glassfish 3.1.2.2,但计时器不像你描述的那样工作。

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


【解决方案1】:

如果只有 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");
        }
    }

}

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

}

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

}

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

【讨论】:

  • 第二个使用 AtomicBoolean 的解决方案不需要第二个 EJB。检查也可以在 TimerBean 中进行。
  • @Arjan Tijms “在单例中,方法默认是写锁定的”——你碰巧有这方面的参考吗?似乎是一个可怕的默认值!作为开发人员,您应该确保您的单例是线程安全的,因此并发访问(读锁)应该是默认的恕我直言。
  • "如果单例类上没有@Lock注解,则默认锁定类型@Lock(WRITE)将应用于所有业务和超时方法。"链接:docs.oracle.com/cd/E19798-01/821-1841/gipsz/index.html
  • 嗨,我面临同样的问题,我在这里发布 [链接]:stackoverflow.com/questions/67001697/… 请看看我应该在这里做什么?
【解决方案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();
        }
    }

}

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

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

【讨论】:

  • 嗨,我也遇到了同样的问题。我尝试了第一种方法,但没有解决问题。您能否提供更多信息/示例,以及您在“doStuff”中提到的特殊情况..?
  • @NomeshDeSilva,我已经更新了代码以允许异常
  • 谢谢。你的修复解决了我的问题。我使用了您的代码,但我使用的是 createCalendarTimer 而不是 createSingleActionTimer。更改为 createSingleActionTimer 后一切正常。
【解决方案3】:

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

@Singleton @Startup @LocalBean 为例,注入standalone.xml 中配置的默认“managed-scheduled-executor-service”:

@Resource
private ManagedScheduledExecutorService scheduledExecutorService;

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

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

scheduleWithFixedDelay:

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

不要关闭调度程序,即@PreDestroy:

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-16
      • 2020-01-07
      • 2016-03-04
      • 2021-11-02
      • 2015-08-09
      • 2013-08-19
      相关资源
      最近更新 更多