如果只有 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 不允许将读锁升级为写锁)。
计时器豆:
@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 中,然后检查那里的布尔值,等等。