Manually call the @Scheduled method

I need advice on the following:

I have a @Scheduled service method that has a fixed date of a couple of seconds, in which it scans the work queue and processes the corresponding work if it finds it. In the same service, I have a method that puts the work on the work queue, and I would like this method to immediately call the queue scan after it has been executed (since I'm sure there will be some work for the scanner now) to avoid delays before scheduled kicks (since it can be seconds, and time is somewhat critical).

The trigger now function of the Task Execution and Scheduling subsystems would be ideal: one of them would also be reset fixedDelay after the execution was manually initiated (since I do not want my manual execution to interfere with the scheduled one). Note: Queuing may come from an external source, so periodic scanning is required.

Any advice is welcome

Edit: The queue is stored in db based on the document, so local queue based solutions are not suitable.

The solution, which I'm not completely satisfied with (I don't really like using raw threads), would look something like this:

@Service public class MyProcessingService implements ProcessingService { Thread worker; @PostCreate public void init() { worker = new Thread() { boolean ready = false; private boolean sleep() { synchronized(this) { if (ready) { ready = false; } else { try { wait(2000); } catch(InterruptedException) { return false; } } } return true; } public void tickle() { synchronized(this) { ready = true; notify(); } } public void run() { while(!interrupted()) { if(!sleep()) continue; scan(); } } } worker.start(); } @PreDestroy public void uninit() { worker.interrup(); } public void addWork(Work work) { db.store(work); worker.tickle(); } public void scan() { List<Work> work = db.getMyWork(); for (Work w : work) { process(); } } public void process(Work work) { // work processing here } } 
+6
source share
1 answer

Since there would be no work for the @Scheduled method if there are no elements in the work queue, that is, if no one puts any work on the queue between execution cycles. In the same note, if any work item was inserted into the work queue (possibly by an external source) immediately after completion of the scheduled execution, the work will not be displayed until the next execution.

In this scenario, you need a consumer queue maker. A queue in which one or more manufacturers are placed in work items, and the consumer takes items from the queue and processes them. Here you want a BlockingQueue . They can be used to solve the consumer-producer problem in a thread-safe manner.

You may have one Runnable that performs the tasks performed by your current @Scheduled method.

 public class SomeClass { private final BlockingQueue<Work> workQueue = new LinkedBlockingQueue<Work>(); public BlockingQueue<Work> getWorkQueue() { return workQueue; } private final class WorkExecutor implements Runnable { @Override public void run() { while (true) { try { // The call to take() retrieves and removes the head of this // queue, // waiting if necessary until an element becomes available. Work work = workQueue.take(); // do processing } catch (InterruptedException e) { continue; } } } } // The work-producer may be anything, even a @Scheduled method @Scheduled public void createWork() { Work work = new Work(); workQueue.offer(work); } } 

And some other Runnable or another class can put elements like this:

 public class WorkCreator { @Autowired private SomeClass workerClass; @Override public void run() { // produce work Work work = new Work(); workerClass.getWorkQueue().offer(work); } } 

I guess the right way to solve the problem is at your fingertips. There are several options / configurations you may have, just look at java.util.concurrent .

Update after question edited

Even if the external source is db, it is still a producer-consumer problem. You can probably call the scan() method whenever you store data in db, and the scan() method can put the data extracted from db into a BlockingQueue .

To address the actual issue of restarting fixedDelay

Actually this is not possible, either start Java or with Spring , if you yourself do not handle the planning part. There are no trigger-now functions. If you have access to Runnable , which performs this task, you can probably call the run() method yourself. But that would be the same as calling a processing method from anywhere, and you really don't need Runnable .

Another possible workaround

 private Lock queueLock = new ReentrantLock(); @Scheduled public void findNewWorkAndProcess() { if(!queueLock.tryLock()) { return; } try { doWork(); } finally { queueLock.unlock(); } } void doWork() { List<Work> work = getWorkFromDb(); // process work } // To be called when new data is inserted into the db. public void newDataInserted() { queueLock.lock(); try { doWork(); } finally { queueLock.unlock(); } } 

newDataInserted() is called when any new data is inserted. If the scheduled execution is in progress, it will wait until it is completed, and then do the work. The lock() call is blocked here, since we know that there is some work in the database, and the scheduled call could be called before the work has been inserted. The call to lock in findNewWorkAndProcess() when non-blocking as, if the lock was obtained by the newDataInserted method, this means that the scheduled method should not be executed.

Well, you can customize as you wish.

+6
source

Source: https://habr.com/ru/post/945703/


All Articles