我想编写一个每分钟执行一次的后台作业(EJB 3.1)。为此,我使用以下注释:
@Schedule(minute = "*/1", hour = "*")
这工作正常。
但是,有时这项工作可能需要超过一分钟。在这种情况下,定时器仍然被触发,导致线程问题。
如果当前执行未完成,是否有可能终止调度程序?
我想编写一个每分钟执行一次的后台作业(EJB 3.1)。为此,我使用以下注释:
@Schedule(minute = "*/1", hour = "*")
这工作正常。
但是,有时这项工作可能需要超过一分钟。在这种情况下,定时器仍然被触发,导致线程问题。
如果当前执行未完成,是否有可能终止调度程序?
如果只有 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,并在那里检查该布尔值等。
我遇到了同样的问题,但解决方法略有不同。
@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();
}
}
}
这通过设置一个任务在将来执行(在这种情况下,在一秒钟内)来工作。在任务结束时,它会再次安排任务。
编辑:更新以将“东西”重构为另一种方法,以便我们可以防范异常,以便始终重新安排计时器
从 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);
创建并执行一个周期性操作,该操作首先在给定的初始延迟之后启用,随后在一个执行终止和下一个执行开始之间具有给定的延迟。[...]
不要在 ie 中关闭调度程序@PreDestroy
:
Managed Scheduled Executor Service 实例由应用服务器管理,因此 Java EE 应用程序被禁止调用任何与生命周期相关的方法。
好吧,我有一个类似的问题。有一个作业应该每 30 分钟运行一次,有时该作业需要 30 多分钟才能完成,在这种情况下,另一个作业实例正在启动,而前一个作业尚未完成。我通过使用一个静态布尔变量来解决它,我的工作将在它开始运行时将其设置为 true,然后在完成时将其设置回 false。由于它是一个静态变量,所有实例将始终看到相同的副本。当您设置和取消设置静态变量时,您甚至可以同步块。类 myjob{ 私有静态布尔 isRunning=false;
public executeJob(){
if (isRunning)
return;
isRunning=true;
//execute job
isRunning=false;
}
}