如果只有 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,并在那里检查该布尔值,等等。