Quartz Scheduler preemptive trigger priority - java
Is it possible to implement some kind of "preemptive" behavior linked to trigger priorities?
I mean, I want a high-priority trigger to interrupt the currently running low-priority job, and run in its place.
I'd like to go further and not just compare trigger priorities on the same job, but on different jobs trying to work on the same "resource", not at the same time but at overlapping times (assuming the "work" takes time to complete).
I didn't find anything "out of the box". Did anyone ever implement something similar?
This is my solution so far (imports removed). Any warnings or improvements?
//
// AN EXAMPLE JOB CLASS
//
public class DevJob implements InterruptableJob {
private final transient Logger log = LoggerFactory.getLogger(getClass());
AtomicReference<Thread> jobThreadHolder = new AtomicReference<Thread>();
#Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String triggerName = jobExecutionContext.getTrigger().getKey().toString();
String jobName = jobExecutionContext.getJobDetail().getKey().toString();
log.debug("Executing Job {}-{} ", triggerName, jobName);
jobThreadHolder.set(Thread.currentThread());
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
int duration = jobDataMap.getInt("duration");
try {
log.debug("Job {}-{} works for {}s...", triggerName, jobName, duration);
Thread.sleep(duration*1000);
} catch (InterruptedException e) {
log.debug("Job {}-{} interrupted", triggerName, jobName);
PreemptiveVolatileQueueQuartzListener.setInterrupted(jobExecutionContext);
} finally {
log.debug("Job {}-{} terminates", triggerName, jobName);
}
}
#Override
public void interrupt() throws UnableToInterruptJobException {
Thread thread = jobThreadHolder.getAndSet(null);
if (thread != null) {
thread.interrupt();
}
}
}
//
// IMPLEMENTATION FOR JOB PREEMPTION
//
/**
* This class implements a priority policy for jobs in the same thread group.
* When a new job starts, it will check if another job in the same thread group is already running.
* In such a case it compares trigger priorities. The job with lower priority is put in a wait queue for the thread group,
* but only if another instance with the same jobKey is not in the queue already.
* When the running job terminates, a new job is pulled from the queue based on priority and timestamp: for equal
* priorities, the older one is executed.
* If a job has been interrupted MAX_RESCHEDULINGS times, it will ignore any further interruptions.
* A job must implement InterruptableJob and periodically call checkInterruptRequested() if it can be interrupted by a
* higher priority job; it could ignore interruptions, in which case the higher priority job will execute only after
* its natural termination.
*/
public class PreemptiveVolatileQueueQuartzListener implements JobListener, TriggerListener {
private final transient Logger log = LoggerFactory.getLogger(getClass());
// The number of times that a low priority job can be preempted by any high priority job before it ignores preemption
private static final int MAX_RESCHEDULINGS = 20;
// This map holds the pointer to the current running job and its deferred queue, for a given thread group
private Map<String, RunningJobHolder> runningJobs = new HashMap<>(); // triggerGroup -> RunningJob
private static final String INTERRUPTED_FLAG = "PREEMPT_INTERRUPTED";
private static final String INTERRUPTREQUESTED_FLAG = "PREEMPT_INTERRUPTREQUESTED";
static final String JOB_ORIG_KEY = "PREEMPT_JOBORIGKEY";
/**
* Call this method to notify a job that an interruption has been requested. It should tipically be called
* in the InterruptableJob.interrupt() method. The job will then have to programmatically check this flag with checkInterruptRequested()
* and exit if the result is true.
*/
public final static void requestInterrupt(JobExecutionContext jobExecutionContext) {
jobExecutionContext.getJobDetail().getJobDataMap().put(INTERRUPTREQUESTED_FLAG, true);
}
/**
* Call this method in a job to check if an interruption has been requested. If the result is true, the "interrupted" flag
* will be set to true and the job should exit immediately
* because it will be rescheduled after the interrupting job has finished.
* #param jobExecutionContext can be null if the check should not be performed
* #return true if the interruption was requested
*/
public final static boolean checkInterruptRequested(JobExecutionContext jobExecutionContext) {
boolean result = false;
if (jobExecutionContext!=null) {
try {
result = jobExecutionContext.getJobDetail().getJobDataMap().getBoolean(INTERRUPTREQUESTED_FLAG);
} catch (Exception e) {
// Ignore, stay false
}
if (result) {
setInterrupted(jobExecutionContext);
}
}
return result;
}
/**
* Call this method in a job when catching an InterruptedException if not rethrowing a JobExecutionException
* #param jobExecutionContext
*/
public final static void setInterrupted(JobExecutionContext jobExecutionContext) {
jobExecutionContext.getJobDetail().getJobDataMap().put(INTERRUPTED_FLAG, true);
}
private final boolean isInterrupted(JobExecutionContext jobExecutionContext) {
try {
return true==jobExecutionContext.getJobDetail().getJobDataMap().getBoolean(INTERRUPTED_FLAG);
} catch (Exception e) {
return false;
}
}
private final void clearInterrupted(JobExecutionContext jobExecutionContext) {
jobExecutionContext.getJobDetail().getJobDataMap().remove(INTERRUPTREQUESTED_FLAG);
jobExecutionContext.getJobDetail().getJobDataMap().remove(INTERRUPTED_FLAG);
}
/**
* This method decides if a job has to start or be queued for later.
*/
#Override
public boolean vetoJobExecution(Trigger startingTrigger, JobExecutionContext startingJobContext) {
log.debug("Calculating veto for job {}", makeJobString(startingTrigger));
boolean veto = false;
String preemptedGroup = startingTrigger.getKey().getGroup();
synchronized (runningJobs) {
veto = calcVeto(startingTrigger, preemptedGroup, startingJobContext);
}
log.debug("veto={} for job {}", veto, makeJobString(startingTrigger));
return veto;
}
private boolean calcVeto(Trigger startingTrigger, String preemptedGroup, JobExecutionContext startingJobContext) {
final boolean VETO = true;
final boolean NOVETO = false;
int startingJobPriority = startingTrigger.getPriority();
RunningJobHolder runningJobHolder = runningJobs.get(preemptedGroup);
if (runningJobHolder==null) {
// No conflicting job is running - just start it
runningJobHolder = new RunningJobHolder();
runningJobs.put(preemptedGroup, runningJobHolder);
PrioritizedJob newJob = runningJobHolder.setActiveJob(startingJobPriority, startingTrigger, startingJobContext);
log.debug("Starting new job {} with nothing in the same group", newJob);
return NOVETO;
}
// Check that the current job isn't a job that has just been pulled from the queue and activated
boolean sameTrigger = startingTrigger.equals(runningJobHolder.activeJob.trigger);
if (sameTrigger) {
// runningJobHolder.activeJob has been set in triggerComplete but the trigger didn't fire until now
log.debug("Starting trigger {} is the same as the active one", startingTrigger.getKey());
return NOVETO;
}
// Check that the starting job is not already running and is not already queued, because we don't want
// jobs to accumulate in the queue (a design choice)
if (runningJobHolder.isInHolder(startingTrigger)) {
log.debug("Starting job {} is queued already (maybe with a different trigger)", makeJobString(startingTrigger));
return VETO;
}
// A job for this triggerGroup is already running and is not the same as the one trying to start and is not in the queue already.
// The starting job is therefore queued, ready to be started, regardless of the priority.
PrioritizedJob newJob = runningJobHolder.queueJob(startingJobPriority, startingTrigger, startingJobContext);
log.debug("New job {} queued", newJob);
printQueue(runningJobHolder); // Debug
if (startingJobPriority>runningJobHolder.activeJob.priority) {
if (runningJobHolder.activeJob.reschedulings >= MAX_RESCHEDULINGS) {
// When a job has been preempted too many times, it is left alone even if at lower priority
log.debug("New job {} does not interrupt job {} of lower priority because its reschedulings are {}", newJob, runningJobHolder.activeJob, runningJobHolder.activeJob.reschedulings);
} else {
// The starting job has a higher priority than the current running job, which needs to be interrupted.
// The new job will take the place of the running job later, because it has been added to the queue already.
// If the running job doesn't react to the interruption, it will complete normally and let the next job in
// the queue to proceed.
log.debug("New job {} interrupts job {} of lower priority", newJob, runningJobHolder.activeJob);
try {
Scheduler scheduler = startingJobContext.getScheduler();
scheduler.interrupt(runningJobHolder.getJobKey());
} catch (UnableToInterruptJobException e) {
log.error("Can't interrupt job {} for higher-priority job {} that will have to wait", runningJobHolder.activeJob, newJob);
}
}
}
return VETO;
}
/**
* The interrupt() method of a InterruptableJob should issue a thread.interrupt() on the job thread,
* and if not handled already, the resulting InterruptedException will be handled here.
*/
#Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
// Just in case a job throws InterruptedException when interrupted.
// Raise a flag if the job was interrupted with InterruptedException
if (jobException!=null && jobException.getCause() instanceof InterruptedException) {
PreemptiveVolatileQueueQuartzListener.setInterrupted(context);
}
}
// Debug method
private void printQueue(RunningJobHolder runningJobHolder) {
if (log.isDebugEnabled()) {
PriorityQueue<PrioritizedJob> clone = new PriorityQueue<PrioritizedJob>();
clone.addAll(runningJobHolder.jobQueue);
log.debug("Priority Queue: {}", clone.isEmpty()?"empty":"");
while (!clone.isEmpty()) {
PrioritizedJob job = clone.poll();
String jobKey = (String) job.trigger.getJobDataMap().getOrDefault(PreemptiveVolatileQueueQuartzListener.JOB_ORIG_KEY, job.trigger.getJobKey().toString());
log.debug("- {} [{}] reschedulings={}", job, jobKey, job.reschedulings);
}
}
}
// When a job finishes execution, a new one is started if the queue is not empty
private boolean startNextJobInQueue(PrioritizedJob terminatedJob, RunningJobHolder runningJobHolder, String preemptedGroup, Trigger usedTrigger, JobExecutionContext context) {
PrioritizedJob queuedJob = runningJobHolder.jobQueue.poll(); // Remove from queue
if (queuedJob!=null) { //
log.debug("Starting next job in queue {} after job {} finished", queuedJob, terminatedJob);
// The job must be cloned with a new trigger to execute immediately.
// Can't reuse the existing jobDetail with a new trigger because for some reason when the original trigger has
// finished all invocations, the following exception is thrown when trying to start the new trigger:
// org.quartz.JobPersistenceException: The job (xxx) referenced by the trigger does not exist.
JobDataMap jobDataMap = queuedJob.jobDetail.getJobDataMap();
JobDataMap triggerJobDataMap = queuedJob.trigger.getJobDataMap();
JobDataMap newTriggerJobDataMap = new JobDataMap(triggerJobDataMap);
// Need to store the original jobKey, used to check if a starting job is already in the queue. I can't use the normal
// jobKey because, when a job is cloned here, its key must be changed in order to store it without a "job already exists" exception.
String jobOrigKey = (String) triggerJobDataMap.getOrDefault(JOB_ORIG_KEY, queuedJob.jobDetail.getKey().toString());
newTriggerJobDataMap.put(JOB_ORIG_KEY, jobOrigKey);
JobDetail newJob = JobBuilder.newJob(queuedJob.jobDetail.getJobClass())
.withIdentity(makePreemptedId(queuedJob.jobDetail.getKey().getName()), queuedJob.jobDetail.getKey().getGroup())
.requestRecovery(queuedJob.jobDetail.requestsRecovery())
.storeDurably(queuedJob.jobDetail.isDurable())
.withDescription(queuedJob.jobDetail.getDescription())
.usingJobData(jobDataMap)
.build();
Trigger newTrigger = newTrigger()
.withPriority(queuedJob.priority)
.withIdentity(makePreemptedId(queuedJob.trigger.getKey().getName()), preemptedGroup)
.usingJobData(newTriggerJobDataMap)
.withDescription(queuedJob.trigger.getDescription())
.startNow()
.withSchedule(simpleSchedule()
// A misfire occurs if a persistent trigger “misses” its firing time because
// of the scheduler being shutdown, or because there are no available threads
.withMisfireHandlingInstructionFireNow() // (Not sure is correct)
)
.build();
try {
context.getScheduler().scheduleJob(newJob, newTrigger);
log.debug("Job {} from queue rescheduled to start now as {}", queuedJob, makeJobString(newTrigger));
queuedJob.reschedulings++;
queuedJob.trigger = newTrigger;
queuedJob.jobDetail = newJob;
runningJobHolder.activeJob = queuedJob;
return true;
} catch (SchedulerException e) {
log.error("Failed to start queued job {}", queuedJob, e);
runningJobHolder.activeJob = null;
return false;
}
}
return false;
}
private String makeJobString(Trigger trigger) {
StringBuilder sb = new StringBuilder(trigger.getKey().toString());
sb.append("-").append(trigger.getJobKey().toString());
return sb.toString();
}
// Each time a job is rescheduled with a new trigger, their names are changed to a (hopefully) unique string
private String makePreemptedId(String oldName) {
final String marker = "_p_r_e_";
long random = ThreadLocalRandom.current().nextLong(999888777L);
StringBuffer result = new StringBuffer(Long.toString(random)); // nnn
int pos = oldName.indexOf(marker);
if (pos>-1) {
result.append(oldName.substring(pos));
} else {
result.append(marker).append(oldName);
}
return result.toString();
}
// Called when a job finishes execution
#Override
public void triggerComplete(Trigger usedTrigger, JobExecutionContext context, CompletedExecutionInstruction completedExecutionInstruction) {
boolean interruptedJob = isInterrupted(context);
if (log.isDebugEnabled()) {
if (interruptedJob) {
log.debug("Interrupted job {}", makeJobString(usedTrigger));
} else {
log.debug("Terminated job {}", makeJobString(usedTrigger));
}
}
String preemptedGroup = usedTrigger.getKey().getGroup();
synchronized (runningJobs) {
RunningJobHolder runningJobHolder = runningJobs.get(preemptedGroup);
// Check that the activeJob is also the one that just terminated - for consistency
if (runningJobHolder==null || !runningJobHolder.getJobKey().equals(context.getJobDetail().getKey())) {
// Should never happen if there aren't any bugs
log.error("Internal Error: the job in triggerComplete {} is not the active job {} for group {} (skipping)",
makeJobString(usedTrigger),
runningJobHolder==null?null:runningJobHolder.activeJob,
preemptedGroup);
return;
}
printQueue(runningJobHolder);
PrioritizedJob terminatedJob = runningJobHolder.activeJob;
clearInterrupted(context);
runningJobHolder.activeJob = null;
// Start the next queued job if any. Do it in a loop because the next job might not start
// properly and this would otherwise prevent the other queued jobs from starting.
boolean started = false;
while (!started && !runningJobHolder.jobQueue.isEmpty()) {
started = startNextJobInQueue(terminatedJob, runningJobHolder, preemptedGroup, usedTrigger, context);
if (interruptedJob && (started || runningJobHolder.jobQueue.isEmpty())) {
// It was an interrupted lower-priority job, so put it in the queue
log.debug("Interrupted job {} added to job queue for rescheduling", terminatedJob);
runningJobHolder.addToQueue(terminatedJob);
interruptedJob=false;
}
}
printQueue(runningJobHolder);
if (runningJobHolder.jobQueue.isEmpty() && runningJobHolder.activeJob == null) {
// The current terminated job was the last one in the trigger group, so we can clean up
log.debug("Job {} ended with an empty proprity queue", terminatedJob);
runningJobs.remove(preemptedGroup);
}
}
}
#Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
}
#Override
public void triggerMisfired(Trigger trigger) {
}
#Override
public String getName() {
return this.getClass().getSimpleName();
}
#Override
public void jobToBeExecuted(JobExecutionContext context) {
}
#Override
public void jobExecutionVetoed(JobExecutionContext context) {
}
}
//
// RELATED CLASSES
//
/**
* A job with associated priority and timestamp
*
*/
class PrioritizedJob implements Comparable<PrioritizedJob> {
int priority;
long creationTimestamp = System.currentTimeMillis(); // To prevent same-priority elements to age
Trigger trigger;
JobDetail jobDetail; // Needed to create a new job when rescheduling after pulling from the queue
int reschedulings = 0; // Number of times the job has been put back in the queue because preempted by a higher-priority job
#Override
public int compareTo(PrioritizedJob o) {
// Smallest PrioritizedJob goes first, so priority check must be inverted because higher priority goes first
int comparison = -(new Integer(this.priority).compareTo(o.priority));
if (comparison==0) {
// lower timestamp is higher priority
comparison = new Long(this.creationTimestamp).compareTo(o.creationTimestamp);
}
return comparison;
}
#Override
public String toString() {
StringBuffer result = new StringBuffer();
result.append(trigger.getKey()).append("-").append(trigger.getJobKey()).append("(").append(priority).append(")");
return result.toString();
}
}
/**
* Holds the current running job definition for a given trigger group
*/
class RunningJobHolder {
PrioritizedJob activeJob; // The running job
// This queue holds all jobs of the same thread group that tried to start while this job was running.
// They are sorted by priority and timestamp.
// The head of the queue might contain a higher-priority job that has been put in the queue while waiting for
// the active job to handle the interruption
PriorityQueue<PrioritizedJob> jobQueue = new PriorityQueue<>();
JobKey getJobKey() {
return activeJob.trigger.getJobKey();
}
/**
* Create a new PrioritizedJob and set it as active
* #param startingJobPriority
* #param startingTrigger
* #param startingJobContext
* #return the new PrioritizedJob
*/
PrioritizedJob setActiveJob(int startingJobPriority, Trigger startingTrigger, JobExecutionContext startingJobContext) {
PrioritizedJob newJob = new PrioritizedJob();
newJob.priority = startingJobPriority;
newJob.trigger = startingTrigger;
newJob.jobDetail = startingJobContext.getJobDetail();
this.activeJob = newJob;
return newJob;
}
/**
* Create a new PrioritizedJob and add it to the queue
* #param startingJobPriority
* #param startingTrigger
* #param startingJobContext
* #return the new PrioritizedJob
*/
PrioritizedJob queueJob(int startingJobPriority, Trigger startingTrigger, JobExecutionContext startingJobContext) {
PrioritizedJob newJob = new PrioritizedJob();
newJob.priority = startingJobPriority;
newJob.trigger = startingTrigger;
newJob.jobDetail = startingJobContext.getJobDetail();
addToQueue(newJob);
return newJob;
}
/**
* Compares job keys, first by fetching the original job key stored in the trigger JobDataMap, then by using the job's own key
* #param trigger
* #param prioritizedJob
* #return
*/
private boolean equalKeys(Trigger trigger, PrioritizedJob prioritizedJob) {
String triggerJobKeyToCheck = (String) trigger.getJobDataMap().getOrDefault(PreemptiveVolatileQueueQuartzListener.JOB_ORIG_KEY, trigger.getJobKey().toString());
String prioritizedJobKeyToCheck = (String) prioritizedJob.trigger.getJobDataMap().getOrDefault(PreemptiveVolatileQueueQuartzListener.JOB_ORIG_KEY, prioritizedJob.trigger.getJobKey().toString());
return triggerJobKeyToCheck.equals(prioritizedJobKeyToCheck);
}
/**
* Check if the job in a trigger has already been queued (or is the active one) by comparing the job key
* #param trigger
* #return
*/
boolean isInHolder(Trigger trigger) {
if (equalKeys(trigger, activeJob)) {
return true;
}
for (PrioritizedJob prioritizedJob : jobQueue) {
if (equalKeys(trigger, prioritizedJob)) {
return true;
}
}
return false;
}
void addToQueue(PrioritizedJob prioritizedJob) {
jobQueue.add(prioritizedJob);
}
}
Related
Processing tasks in parallel and sequentially Java
In my program, the user can trigger different tasks via an interface, which take some time to process. Therefore they are executed by threads. So far I have implemented it so that I have an executer with one thread that executes all tasks one after the other. But now I would like to parallelize everything a little bit. i.e. I would like to run tasks in parallel, except if they have the same path, then I want to run them sequentially. For example, I have 10 threads in my pool and when a task comes in, the task should be assigned to the worker which is currently processing a task with the same path. If no task with the same path is currently being processed by a worker, then the task should be processed by a currently free worker. Additional info: A task is any type of task that is executed on a file in the local file system. For example, renaming a file. Therefore, the task have the attribute path. And I don't want to execute two tasks on the same file at the same time, so such tasks with the same paths should be performed sequentially. Here is my sample code but there is work to do: One of my problems is, I need a safe way to check if a worker is currently running and get the path of the currently running worker. By safe I mean, that no problems of simultaneous access or other thread problems occur. public class TasksOrderingExecutor { public interface Task extends Runnable { //Task code here String getPath(); } private static class Worker implements Runnable { private final LinkedBlockingQueue<Task> tasks = new LinkedBlockingQueue<>(); //some variable or mechanic to give the actual path of the running tasks?? private volatile boolean stopped; void schedule(Task task) { tasks.add(task); } void stop() { stopped = true; } #Override public void run() { while (!stopped) { try { Task task = tasks.take(); task.run(); } catch (InterruptedException ie) { // perhaps, handle somehow } } } } private final Worker[] workers; private final ExecutorService executorService; /** * #param queuesNr nr of concurrent task queues */ public TasksOrderingExecutor(int queuesNr) { Preconditions.checkArgument(queuesNr >= 1, "queuesNr >= 1"); executorService = new ThreadPoolExecutor(queuesNr, queuesNr, 0, TimeUnit.SECONDS, new SynchronousQueue<>()); workers = new Worker[queuesNr]; for (int i = 0; i < queuesNr; i++) { Worker worker = new Worker(); executorService.submit(worker); workers[i] = worker; } } public void submit(Task task) { Worker worker = getWorker(task); worker.schedule(task); } public void stop() { for (Worker w : workers) w.stop(); executorService.shutdown(); } private Worker getWorker(Task task) { //check here if a running worker with a specific path exists? If yes return it, else return a free worker. How do I check if a worker is currently running? return workers[task.getPath() //HERE I NEED HELP//]; } }
Seems like you have a pair of problems: You want to check the status of tasks submitted to an executor service You want to run tasks in parallel, and possibly prioritize them Future For the first problem, capture the Future object returned when you submit a task to an executor service. You can check the Future object for its completion status. Future< Task > future = myExecutorService.submit( someTask ) ; … boolean isCancelled = future.isCancelled() ; // Returns true if this task was cancelled before it completed normally. boolean isDone = future.isDone(); // Returns true if this task completed. The Future is of a type, and that type can be your Task class itself. Calling Future::get yields the Task object. You can then interrogate that Task object for its contained file path. Task task = future.get() ; String path = task.getPath() ; // Access field via getter from your `Task` object. Executors Rather than instantiating new ThreadPoolExecutor, use the Executors utility class to instantiate an executor service on your behalf. Instantiating ThreadPoolExecutor directly is not needed for most common scenarios, as mentioned in the first line of its Javadoc. ExecutorService es = Executors.newFixedThreadPool( 3 ) ; // Instantiate an executor service backed by a pool of three threads. For the second problem, use an executor service backed by a thread pool rather than a single thread. The executor service automatically assigns the submitted task to an available thread. As for grouping or prioritizing, use multiple executor services. You can instantiate more than one. You can have as many executor services as you want, provided you do not overload the demand on your deployment machine for CPU cores and memory (think about your maximum simultaneous usage). ExecutorService esSingleThread = Executors.newSingleThreadExecutor() ; ExecutorService esMultiThread = Executors.newCachedThreadPool() ; One executor service might be backed by a single thread to limit the demands on the deployment computer, while others might be backed by a thread pool to get more work done. You can use these multiple executor services as your multiple queues. No need for you to be managing queues and workers as seen in the code of your Question. Executors were invented to further simplify working with multiple threads. Concurrency You said: And I don't want to execute two tasks on the same file at the same time, so such tasks with the same paths should be performed sequentially. You should have a better way to handle the concurrency conflict that just scheduling tasks on threads. Java has ways to manage concurrent access to files. Search to learn more, as this has been covered on Stack Overflow already. Perhaps I have not understood fully your needs, so do comment if I am off-base.
It seems that you need some sort of "Task Dispatcher" that executes or holds some tasks depending on some identifier (here the Path of the file the task is applied to). You could use something like this : public class Dispatcher<I> implements Runnable { /** * The executor used to execute the submitted task */ private final Executor executor; /** * Map of the pending tasks */ private final Map<I, Deque<Runnable>> pendingTasksById = new HashMap<>(); /** * set containing the id that are currently executed */ private final Set<I> runningIds = new HashSet<>(); /** * Action to be executed by the dispatcher */ private final BlockingDeque<Runnable> actionQueue = new LinkedBlockingDeque<>(); public Dispatcher(Executor executor) { this.executor = executor; } /** * Task in the same group will be executed sequentially (but not necessarily in the same thread) * #param id the id of the group the task belong * #param task the task to execute */ public void submitTask(I id, Runnable task) { actionQueue.addLast(() -> { if (canBeLaunchedDirectly(id)) { executeTask(id, task); } else { addTaskToPendingTasks(id, task); ifPossibleLaunchPendingTaskForId(id); } }); } #Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { actionQueue.takeFirst().run(); } catch (InterruptedException e) { Thread.currentThread().isInterrupted(); break; } } } private void addTaskToPendingTasks(I id, Runnable task) { this.pendingTasksById.computeIfAbsent(id, i -> new LinkedList<>()).add(task); } /** * #param id an id of a group * #return true if a task of the group with the provided id is currently executed */ private boolean isRunning(I id) { return runningIds.contains(id); } /** * #param id an id of a group * #return an optional containing the first pending task of the group, * an empty optional if no such task is available */ private Optional<Runnable> getFirstPendingTask(I id) { final Deque<Runnable> pendingTasks = pendingTasksById.get(id); if (pendingTasks == null) { return Optional.empty(); } assert !pendingTasks.isEmpty(); final Runnable result = pendingTasks.removeFirst(); if (pendingTasks.isEmpty()) { pendingTasksById.remove(id); } return Optional.of(result); } private boolean canBeLaunchedDirectly(I id) { return !isRunning(id) && pendingTasksById.get(id) == null; } private void executeTask(I id, Runnable task) { this.runningIds.add(id); executor.execute(() -> { try { task.run(); } finally { actionQueue.addLast(() -> { runningIds.remove(id); ifPossibleLaunchPendingTaskForId(id); }); } }); } private void ifPossibleLaunchPendingTaskForId(I id) { if (isRunning(id)) { return; } getFirstPendingTask(id).ifPresent(r -> executeTask(id, r)); } } To use it, you need to launch it in a separated thread (or you can adapt it for a cleaner solution) like this : final Dispatcher<Path> dispatcher = new Dispatcher<>(Executors.newCachedThreadPool()); new Thread(dispatcher).start(); dispatcher.submitTask(path, task1); dispatcher.submitTask(path, task2); This is basic example, you might need to keep the thread and even better wrap all of that in a class.
all you need is a hash map of actors, with file path as a key. Different actors would run in parallel, and concrete actor would handle tasks sequentially. Your solution is wrong because Worker class uses blocking operation take but is executed in a limited thread pool, which may lead to a thread starvation (a kind of deadlock). Actors do not block when waiting for next message. import org.df4j.core.dataflow.ClassicActor; import java.util.HashMap; import java.util.Map; import java.util.concurrent.*; public class TasksOrderingExecutor { public static class Task implements Runnable { private final String path; private final String task; public Task(String path, String task) { this.path = path; this.task = task; } //Task code here String getPath() { return path; } #Override public void run() { System.out.println(path+"/"+task+" started"); try { Thread.sleep(500); } catch (InterruptedException e) { } System.out.println(path+"/"+task+" stopped"); } } static class Worker extends ClassicActor<Task> { #Override protected void runAction(Task task) throws Throwable { task.run(); } } private final ExecutorService executorService; private final Map<String,Worker> workers = new HashMap<String,Worker>(){ #Override public Worker get(Object key) { return super.computeIfAbsent((String) key, (k) -> { Worker res = new Worker(); res.setExecutor(executorService); res.start(); return res; }); } }; /** * #param queuesNr nr of concurrent task queues */ public TasksOrderingExecutor(int queuesNr) { executorService = ForkJoinPool.commonPool(); } public void submit(Task task) { Worker worker = getWorker(task); worker.onNext(task); } public void stop() throws InterruptedException { for (Worker w : workers.values()) { w.onComplete(); } executorService.shutdown(); executorService.awaitTermination(10, TimeUnit.SECONDS); } private Worker getWorker(Task task) { //check here if a runnig worker with a specific path exists? If yes return it, else return a free worker. How do I check if a worker is currently running? return workers.get(task.getPath()); } public static void main(String[] args) throws InterruptedException { TasksOrderingExecutor orderingExecutor = new TasksOrderingExecutor(20); orderingExecutor.submit(new Task("path1", "task1")); orderingExecutor.submit(new Task("path1", "task2")); orderingExecutor.submit(new Task("path2", "task1")); orderingExecutor.submit(new Task("path3", "task1")); orderingExecutor.submit(new Task("path2", "task2")); orderingExecutor.stop(); } } The protocol of execution shows that tasks with te same key are executed sequentially and tasks with different keys are executed in parallel: path3/task1 started path2/task1 started path1/task1 started path3/task1 stopped path2/task1 stopped path1/task1 stopped path2/task2 started path1/task2 started path2/task2 stopped path1/task2 stopped I used my own actor library DF4J, but any other actor library can be used.
Java Multi-threading: make a thread to start after checking the returned value from the first thread
I have a thread that basically makes a connection to the server and if the connection is successful, it will return a positive ID number. I would like to create another thread that will check if the current ID number is positive and runs once it detects the ID is positive. // My first thread that establishes connection new Thread() { public void run(){ makeConnection(); // this makeConnection method will make the ID become a positive number if the connection is fully established. } }.start(); Note that obj.getCurrentId() returns the current ID number. But I am struggling to write the second thread and how it communicates with the first thread. Can someone kindly help me out please? Thanks.
Assuming that you use Java 8 a good way to implement it is with CompletableFuture as it will allow you to define a flow of asynchronous tasks to execute. So for example here the main code could be: // Call connect asynchronously using the common pool from a given thread // then execute someMethod using another thread CompletableFuture.supplyAsync(MyClass::connect) .thenCompose(MyClass::someMethodAsync); The method connect of the class MyClass could be: public static int connect() { try { SomeClass obj = makeConnection(); // ok so we return a positive value return obj.getCurrentId(); } catch (Exception e) { // Do something here } // ko so we return a negative value return -1; } The method someMethodAsync of the class MyClass could be: public static CompletionStage<Void> someMethodAsync(int id) { return CompletableFuture.supplyAsync(() -> MyClass.someMethod(id)); } The method someMethod of the class MyClass could be: public static Void someMethod(int id) { if (id > 0) { // do something } return null; } Another approach could be to rely on wait/notify/notifyAll or await/signal/signalAll to notify the other thread that the id has changed. So your code could be something like that: public class SomeClass { /** * The current id */ private int currentId; /** * The object's monitor */ private final Object monitor = new Object(); /** * #return the current id */ public int getCurrentId() { synchronized (monitor) { return this.currentId; } } /** * Sets the current id and notifies waiting threads */ public void setCurrentId(final int currentId) { synchronized (monitor) { this.currentId = currentId; monitor.notifyAll(); } } /** * Makes the calling thread wait until the id is positive * #throws InterruptedException if current thread is interrupted while waiting */ public void waitForPositiveId() throws InterruptedException { synchronized (monitor) { while (currentId <= 0) { monitor.wait(); } } } } So your first thread will simply call makeConnection() assuming that internally it calls the setter setCurrentId of SomeClass and the second thread will start by calling waitForPositiveId() to make it wait until the id is positive. NB: This approach will make the second thread wait for ever if makeConnection() fails.
Few suggestions: Create an ExecutorService Submit the first task : ConnectionTask and get the result Submit the second task: ValidationTask and get the result Depending on the result you can take next set of actions. Sample code: import java.util.concurrent.*; import java.util.*; public class CallablePollingDemo{ public CallablePollingDemo(){ System.out.println("creating service"); ExecutorService service = Executors.newFixedThreadPool(2); try{ Future future1 = service.submit(new ConnectionTask()); int result1 = ((Integer)future1.get()).intValue(); System.out.println("Result from ConnectionTask task:"+result1); if ( result1 > 0){ // change this condition to suit your requirement Future future2 = service.submit(new ValidationTask(result1)); int result2 = ((Integer)future2.get()).intValue(); System.out.println("Result from ValidationTask task:"+result2); } }catch(Exception err){ err.printStackTrace(); } service.shutdown(); } public static void main(String args[]){ CallablePollingDemo demo = new CallablePollingDemo(); } class ConnectionTask implements Callable<Integer>{ public ConnectionTask(){ } public Integer call(){ int id = 1; // Add your business logic here , make connection, get the result return id; } } class ValidationTask implements Callable<Integer>{ Integer id = 0; public ValidationTask(Integer val){ this.id = val; } public Integer call(){ // Add your verification result ehre if ( id > 0 ) { return id; }else{ return -1; } } } }
I recommend using ExecutorService with Callable interface - just return your ID number in Future result. Have a look at ExecutorService.html#submit
Veto execution if job is already executing
I want to skip a job execution if another instance of the same job is already executing (if the same "JobDetail" is running). I know the annotation "#DisallowConcurrentExecution" avoid concurrent execution of the same job. But the downside is that when the job (a job that executes is a longer time then the periodicity of the trigger) finishes the job will be instantly re-executed. I want the job to be executed in the next schedule fire time. Example Job Implementation public class LongJob implements Job { #Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { try { System.out.println("Executed: " + LocalDateTime.now().toString()); TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } } Scheduling JobDetail job2 = JobBuilder.newJob(LongJob.class) .withIdentity("job2") .build(); Trigger trigger2 = TriggerBuilder.newTrigger() .forJob(job2) .startNow() .withIdentity("job2-trigger") .withSchedule( CronScheduleBuilder.cronSchedule("0/3 * * ? * *") .withMisfireHandlingInstructionIgnoreMisfires()) .build(); scheduler.scheduleJob(job2, trigger2); Expected output Executed: 2016-02-18T10:11:15 Executed: 2016-02-18T10:11:21 Executed: 2016-02-18T10:11:27 Executed: 2016-02-18T10:11:33 Output using #DisallowConcurrentExecution Executed: 2016-02-18T10:11:15 Executed: 2016-02-18T10:11:20 Executed: 2016-02-18T10:11:25 Executed: 2016-02-18T10:11:30 My Solution I made a solution using Trigger Listeners, But I was wondering if there is a simpler one. In this approach I would have use a Listener Instance for each group of Trigger that fires the same job (I can solve my "bigger" problem using different trigger for different, avoiding the use of the same trigger for different jobs). class CustomTriggerListener extends TriggerListenerSupport { private JobExecutionContext lastJobExecutionContext; #Override public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { boolean vetoExecution = false; if (lastJobExecutionContext == null) { lastJobExecutionContext = context; } else { boolean lastJobIsDone = lastJobExecutionContext.getJobRunTime() >= 0; if (lastJobIsDone) { lastJobExecutionContext = context; } else { vetoExecution = true; } } return vetoExecution; } #Override public String getName() { return "CustomTriggerListener"; } }
Generic array creation for java.lang.Thread
Intellij IDEA is complaining about Generic array creation public abstract class BaseImageLoader<CacheItem> { private ImageLoaderThread[] workerThreads; public BaseImageLoader(Context context) { ... workerThreads = new ImageLoaderThread[DEFAULT_CACHE_THREAD_POOL_SIZE];//Generic array creation error ... } } ImageLoaderThread is in fact a subclass of java.lang.Thread, its not generic I dont get what im doing wrong this works fine: Thread[] threads = new Thread[DEFAULT_CACHE_THREAD_POOL_SIZE]; ImageLoaderThread class private class ImageLoaderThread extends Thread { /** * The queue of requests to service. */ private final BlockingQueue<ImageData> mQueue; /** * Used for telling us to die. */ private volatile boolean mQuit = false; /** * Creates a new cache thread. You must call {#link #start()} * in order to begin processing. * * #param queue Queue of incoming requests for triage */ public ImageLoaderThread(BlockingQueue<ImageData> queue) { mQueue = queue; } /** * Forces this thread to quit immediately. If any requests are still in * the queue, they are not guaranteed to be processed. */ public void quit() { mQuit = true; interrupt(); } #Override public void run() { android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); ImageData data; while (true) { try { // Take a request from the queue. data = mQueue.take(); } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } ... //other unrelated stuff } } }
ScheduledThreadPoolExecutor.remove : Is it safe to use
i am trying to schedule bunch of tasks to execute periodically. under certain situations some task need to be stopped from scheduling, so i remove them from the interal queue of threadPoolExecutor. I do that from within the task itself Below is my approach. I am not sure the idea of removing the task from the threadPoolExecutor service, from inside of the task can cause any problem.(look at the synchronized method name 'removeTask'. Is there a better way to accomplish what i am trying to do here. public class SchedulerDaemon { private ScheduledExecutorService taskScheduler; private ScheduledFuture taskResult1, taskResult2; private Task1 task1; private Task2 task2; public SchedulerDaemon(Task1 task, Task2 task2) { this.task1 = task1; this.task2 = task2;1 taskScheduler = new ScheduledThreadPoolExecutor(1); } public void start() { if(taskScheduler == null) { taskScheduler = new ScheduledThreadPoolExecutor(1); taskResult = taskScheduler.scheduleAtFixedRate(new TaskWrapper(task1) , 60000,60000, TimeUnit.MILLISECONDS); taskResult2 = taskScheduler.scheduleAtFixedRate(new TaskWrapper(task2) , 60000,60000, TimeUnit.MILLISECONDS); } } public void stop() { if(taskScheduler != null) { taskScheduler.shutdown(); taskResult1.cancel(false); taskResult2.cancel(false); taskScheduler = null; taskResult = null; } } public synchronized void removeTask( TaskWrapper task){ ((ScheduledThreadPoolExecutor) taskScheduler).remove(task); } class TaskWrapper implements Runnable { private Task myTask; public TaskWrapper(Task task) { myTask = task; } #Override public void run() { try { boolean keepRunningTask = myTask.call(); if(!keepRunningTask) { ***//Should this cause any problem??*** removeTask(this); } } catch (Exception e) { //the task threw an exception remove it from execution queue ***//Should this cause any problem??*** removeTask(this); } } } } public Task1 implements Callable<Boolean> { public Boolean call() { if(<something>) return true; else return false; } } public Task2 implements Callable<Boolean> { public Boolean call() { if(<something>) return true; else return false; } }
Whenever you schedule a task ScheduledFuture<?> future = schedulerService.scheduleAtFixedRate(new AnyTask()); Future Object is returned. Use this Future Object to cancel this Task. try this future.cancel(true); from JavaDocs /** * Attempts to cancel execution of this task. This attempt will * fail if the task has already completed, has already been cancelled, * or could not be cancelled for some other reason. If successful, * and this task has not started when <tt>cancel</tt> is called, * this task should never run. If the task has already started, * then the <tt>mayInterruptIfRunning</tt> parameter determines * whether the thread executing this task should be interrupted in * an attempt to stop the task. * * <p>After this method returns, subsequent calls to {#link #isDone} will * always return <tt>true</tt>. Subsequent calls to {#link #isCancelled} * will always return <tt>true</tt> if this method returned <tt>true</tt>. * * #param mayInterruptIfRunning <tt>true</tt> if the thread executing this * task should be interrupted; otherwise, in-progress tasks are allowed * to complete * #return <tt>false</tt> if the task could not be cancelled, * typically because it has already completed normally; * <tt>true</tt> otherwise */
Canceling a task by force is dangerous, that is why stop is mark to remove from java, so, in alternative you should have a shared flag in your thread... something like: can i live? can i live? no? ok return! this seam hugely but is the safe way!