java-ee - every - timer bean example
EJB @Schedule espere hasta que se complete el método (4)
Bueno, tuve un problema similar. Había un trabajo que se suponía que debía ejecutarse cada 30 minutos y, a veces, el trabajo tardaba más de 30 minutos en completarse; en este caso, comenzaba otra instancia de trabajo, mientras que la anterior aún no había terminado. Lo resolví teniendo una variable booleana estática que mi trabajo establecería en verdadero cada vez que comenzaba a ejecutarse y luego lo configuraba de nuevo en falso cuando terminaba. Como es una variable estática, todas las instancias verán la misma copia en todo momento. Incluso puede sincronizar el bloque cuando configura y desarma la variable estática. class myjob {private static boolean isRunning = false;
public executeJob(){
if (isRunning)
return;
isRunning=true;
//execute job
isRunning=false;
}
}
Quiero escribir un trabajo de fondo (EJB 3.1), que se ejecuta cada minuto. Para esto utilizo la siguiente anotación:
@Schedule(minute = "*/1", hour = "*")
que está funcionando bien
Sin embargo, a veces el trabajo puede tomar más de un minuto. En este caso, el temporizador todavía está activado, lo que causa problemas de subprocesamiento.
¿Es de alguna manera posible terminar el planificador si la ejecución actual no se completa?
Desde Java EE 7 es posible usar un ManagedScheduledExecutorService "enterado de EE", es decir, en WildFly:
En, por ejemplo, @Singleton @Startup @LocalBean
, @Singleton @Startup @LocalBean
el predeterminado "managed-scheduled-executor-service" configurado en standalone.xml
:
@Resource
private ManagedScheduledExecutorService scheduledExecutorService;
Programe alguna tarea en @PostConstruct
para ser ejecutada, es decir, cada segundo con retraso fijo :
scheduledExecutorService.scheduleWithFixedDelay(this::someMethod, 1, 1, TimeUnit.SECONDS);
Crea y ejecuta una acción periódica que se habilita primero después de la demora inicial dada, y posteriormente con la demora dada entre la finalización de una ejecución y el comienzo de la siguiente. [...]
No apague el programador en ie @PreDestroy
:
El servidor de aplicaciones administra las instancias del Servicio de ejecución programada administrada, por lo que las aplicaciones Java EE tienen prohibido invocar cualquier método relacionado con el ciclo de vida.
Me encontré con el mismo problema, pero lo resolvió de manera ligeramente diferente.
@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();
}
}
}
Esto funciona configurando una tarea para ejecutar en el futuro (en este caso, en un segundo). Al final de la tarea, programa la tarea nuevamente.
EDITAR: se actualizó para refactorizar las "cosas" en otro método, de modo que podamos proteger las excepciones para que la reprogramación del temporizador siempre suceda
Si solo 1 temporizador puede estar activo al mismo tiempo, hay un par de soluciones.
En primer lugar, el @Timer
probablemente debería estar presente en @Singleton
. En un Singleton, los métodos están bloqueados por escritura de forma predeterminada, por lo que el contenedor se bloqueará automáticamente cuando intente invocar el método del temporizador mientras aún haya actividad en él.
El siguiente es básicamente suficiente:
@Singleton
public class TimerBean {
@Schedule(second= "*/5", minute = "*", hour = "*", persistent = false)
public void atSchedule() throws InterruptedException {
System.out.println("Called");
Thread.sleep(10000);
}
}
atSchedule
está bloqueado por escritura de forma predeterminada y solo puede haber un hilo activo en él, incluidas las llamadas iniciadas por el contenedor.
Tras ser bloqueado, el contenedor puede volver a intentar el temporizador, por lo que para evitarlo usaría un bloqueo de lectura en su lugar y delegaría en un segundo bean (el segundo bean es necesario porque EJB 3.1 no permite actualizar un bloqueo de lectura a un bloqueo de escritura).
El contador de tiempo 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");
}
}
}
El frijol trabajador:
@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");
}
}
Es probable que esto siga imprimiendo una excepción ruidosa en el registro, por lo que una solución más detallada pero más silenciosa es usar un booleano explícito:
El contador de tiempo Bean:
@Singleton
public class TimerBean {
@EJB
private WorkerBean workerBean;
@Lock(READ)
@Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
public void atSchedule() {
workerBean.doTimerWork();
}
}
El frijol trabajador:
@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);
}
}
}
Hay algunas variaciones más posibles, por ejemplo, puede delegar la verificación de ocupado a un interceptor, o inyectar un singleton que solo contiene el booleano en el bean timer, y verificar que boolean allí, etc.