EJB @Schedule wait until method completed

后端 未结 4 769
陌清茗
陌清茗 2020-11-30 22:52

I want to write a back-ground job (EJB 3.1), which executes every minute. For this I use the following annotation:

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


        
相关标签:
4条回答
  • 2020-11-30 22:58

    Since Java EE 7 it is possible to use an "EE-aware" ManagedScheduledExecutorService, i.e. in WildFly:

    In for example a @Singleton @Startup @LocalBean, inject the default "managed-scheduled-executor-service" configured in standalone.xml:

    @Resource
    private ManagedScheduledExecutorService scheduledExecutorService;
    

    Schedule some task in @PostConstruct to be executed i.e. every second with fixed delay:

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

    scheduleWithFixedDelay:

    Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently with the given delay between the termination of one execution and the commencement of the next.[...]

    Do not shutdown the scheduler in i.e. @PreDestroy:

    Managed Scheduled Executor Service instances are managed by the application server, thus Java EE applications are forbidden to invoke any lifecycle related method.

    0 讨论(0)
  • 2020-11-30 23:05

    I ran into the same problem but solved it slightly differently.

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

    This works by setting up a task to execute in the future (in this case, in one second). At the end of the task, it schedules the task again.

    EDIT: Updated to refactor the "stuff" into another method so that we can guard for exceptions so that the rescheduling of the timer always happens

    0 讨论(0)
  • 2020-11-30 23:06

    well I had a similar problem. There was a job that was supposed to run every 30 minutes and sometimes the job was taking more than 30 minutes to complete in this case another instance of job was starting while previous one was not yet finished. I solved it by having a static boolean variable which my job would set to true whenever it started run and then set it back to false whenever it finished. Since its a static variable all instances will see the same copy at all times. You could even synchronize the block when u set and unset the static variable. class myjob{ private static boolean isRunning=false;

    public executeJob(){
    if (isRunning)
        return;
    isRunning=true;
    //execute job
    isRunning=false;
      }
    
    }
    
    0 讨论(0)
  • 2020-11-30 23:22

    If only 1 timer may ever be active at the same time, there are a couple of solutions.

    First of all the @Timer should probably be present on an @Singleton. In a Singleton methods are by default write-locked, so the container will automatically be locked-out when trying to invoke the timer method while there's still activity in it.

    The following is basically enough:

    @Singleton
    public class TimerBean {
    
        @Schedule(second= "*/5", minute = "*", hour = "*", persistent = false)
        public void atSchedule() throws InterruptedException {
    
            System.out.println("Called");
            Thread.sleep(10000);
        }
    }
    

    atSchedule is write-locked by default and there can only ever be one thread active in it, including calls initiated by the container.

    Upon being locked-out, the container may retry the timer though, so to prevent this you'd use a read lock instead and delegate to a second bean (the second bean is needed because EJB 3.1 does not allow upgrading a read lock to a write lock).

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

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

    This will likely still print a noisy exception in the log, so a more verbose but more silently solution is to use an explicit boolean:

    The timer bean:

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

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

    There are some more variations possible, e.g. you could delegate the busy check to an interceptor, or inject a singleton that only contains the boolean into the timer bean, and check that boolean there, etc.

    0 讨论(0)
提交回复
热议问题