web-dev-qa-db-fra.com

EJB @Schedule attendre la fin de la méthode

Je veux écrire un travail de fond (EJB 3.1), qui s'exécute toutes les minutes. Pour cela, j'utilise l'annotation suivante:

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

qui fonctionne bien.

Cependant, le travail peut parfois prendre plus d'une minute. Dans ce cas, le minuteur est toujours déclenché, provoquant des problèmes de threading.

Est-il possible de terminer le planificateur si l'exécution en cours n'est pas terminée?

36
Phil P.

Si un seul temporisateur peut être actif en même temps, il existe deux solutions.

Tout d'abord, le @Timer devrait probablement être présent sur un @Singleton. Dans un Singleton, les méthodes sont par défaut verrouillées en écriture, de sorte que le conteneur sera automatiquement verrouillé lorsque vous essayez d'appeler la méthode timer alors qu'il y a encore de l'activité.

Ce qui suit est fondamentalement suffisant:

@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 verrouillé en écriture par défaut et il ne peut y avoir qu'un seul thread actif, y compris les appels initiés par le conteneur.

Une fois verrouillé, le conteneur peut réessayer le minuteur, donc pour éviter cela, vous utiliseriez un verrou de lecture à la place et délégueriez à un deuxième bean (le deuxième bean est nécessaire car EJB 3.1 ne permet pas de mettre à niveau un verrou de lecture vers un verrouillage en écriture).

Le bean timer:

@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");
        }
    }

}

Le haricot ouvrier:

@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");
    }
}

Cela affichera probablement une exception bruyante dans le journal, donc une solution plus détaillée mais plus silencieuse consiste à utiliser un booléen explicite:

Le bean timer:

@Singleton
public class TimerBean {

    @EJB
    private WorkerBean workerBean;

    @Lock(READ)
    @Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
    public void atSchedule() {
        workerBean.doTimerWork();
    }

}

Le haricot ouvrier:

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

}

Il existe d'autres variantes possibles, par exemple vous pouvez déléguer la vérification occupée à un intercepteur, ou injecter un singleton qui ne contient que le booléen dans le bean timer, et vérifier ce booléen là, etc.

63
Arjan Tijms

J'ai rencontré le même problème mais je l'ai résolu légèrement différemment.

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

}

Cela fonctionne en configurant une tâche à exécuter à l'avenir (dans ce cas, en une seconde). À la fin de la tâche, il planifie à nouveau la tâche.

EDIT: mis à jour pour refactoriser les "trucs" dans une autre méthode afin que nous puissions garder des exceptions afin que le rééchelonnement du temporisateur se produise toujours

7
drone.ah

Depuis Java EE 7, il est possible d'utiliser un "EE-aware" ManagedScheduledExecutorService , c'est-à-dire dans WildFly:

Dans par exemple un @Singleton @Startup @LocalBean, injectez le "service d'exécuteur-programmé-géré" par défaut configuré dans standalone.xml:

@Resource
private ManagedScheduledExecutorService scheduledExecutorService;

Planifiez une tâche dans @PostConstruct à exécuter, c'est-à-dire toutes les secondes avec retard fixe :

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

scheduleWithFixedDelay :

Crée et exécute une action périodique qui devient activée d'abord après le délai initial donné, puis avec le délai donné entre la fin d'une exécution et le début de la suivante. [...]

N'arrêtez pas le planificateur dans, par exemple @PreDestroy:

Les instances de Managed Scheduled Executor Service sont gérées par le serveur d'applications, donc Java EE ne sont pas autorisées à invoquer une méthode liée au cycle de vie.

5
Torsten Römer

eh bien j'ai eu un problème similaire. Il y avait un travail qui était censé s'exécuter toutes les 30 minutes et parfois le travail prenait plus de 30 minutes pour se terminer dans ce cas, une autre instance de travail commençait alors que la précédente n'était pas encore terminée. Je l'ai résolu en ayant une variable booléenne statique que mon travail définirait sur true chaque fois qu'il commencerait à fonctionner, puis la redéfinirait sur false à la fin. Puisqu'il s'agit d'une variable statique, toutes les instances verront la même copie à tout moment. Vous pouvez même synchroniser le bloc lorsque vous définissez et désactivez la variable statique. class myjob {private static boolean isRunning = false;

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

}
0
Rizwan Shaikh