All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jtrim2.executor.TaskScheduler Maven / Gradle / Ivy

There is a newer version: 2.0.7
Show newest version
package org.jtrim2.executor;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicReference;
import org.jtrim2.utils.ExceptionHelper;

/**
 * Allows tasks to be {@link #scheduleTask(Runnable) scheduled} then be
 * {@link #dispatchTasks() executed} later by an {@link Executor} or
 * {@link TaskExecutor}. The tasks will be submitted to the executor in the
 * order they were scheduled.
 * 

* The main benefit of using this class is that scheduling a task is * synchronization transparent and therefore can be called while a lock * is held. Since submitting task happens in the same order as the scheduling * this can be used to invoke event handlers because with events it is usually * important to notify the listeners in the order the events actually occurred. *

* It is important to note that to achieve the aforementioned goals tasks will * never be submitted concurrently, they will be executed one after another. * An additional useful side-effect is that tasks will not even be submitted * while another task is being executed on the current thread avoiding another * possible source of problems. To clarify this, see the following example: *

{@code
 * void doPrint() {
 *   final TaskScheduler scheduler;
 *   scheduler = new TaskScheduler(SyncTaskExecutor.getSimpleExecutor());
 *   scheduler.scheduleTask(() -> {
 *     System.out.print("2");
 *     scheduler.scheduleTask(() -> System.out.print("4"));
 *     scheduler.dispatchTasks(); // In this case, this is a no-op
 *     System.out.print("3");
 *   });
 *   System.out.print("1");
 *   // The next method call will execute both the above scheduled tasks.
 *   scheduler.dispatchTasks();
 *   System.out.print("5");
 * }
 * }
* The above {@code doPrint()} method will always print "12345" and the * {@code scheduler.dispatchTasks()} call in the scheduled task will actually * do nothing in this case but return immediately. *

* The behaviour of this class can also be exploited to "synchronize" actions * without locking, so even tasks not being synchronization transparent * and also not thread-safe can be used safely using this class. This is the * feature what the {@link TaskExecutors#inOrderExecutor(TaskExecutor)} and * {@link TaskExecutors#inOrderSyncExecutor()} classes provide. * *

Dangers of using this class

* At first blink it seems tempting to use this class instead of locks because * unlike with locks, methods of this class never block and cannot cause * dead-locks unless the submitted tasks wait for each other. While this is * true, there are three issues to consider: *
    *
  • * Using {@code TaskScheduler} has a higher per task overhead than using a * lock. *
  • *
  • * Usually there is no telling in which {@code dispatchTasks()} method will * a particular task execute. This may add some additional non-determinism, * making debugging possibly harder. *
  • *
  • * Using locks cause tasks (and the executing threads) to wait for each other, * resulting in a natural throttling. {@code TaskScheduler} however maintains * a list of not yet executed tasks and this can lead to * {@code OutOfMemoryError} if tasks are scheduled faster than can be * submitted to the executor by a single thread. Notice that with locks * the number of such tasks waiting to be executed is limited by the number * of threads (which should not be too high). *
  • *
* Therefore only use this class when locks are not an option. That is, favor * locks when they are safe to use. One of the good use of this class is to * notify event listeners. * *

Thread safety

* The methods of this class are safe to use by multiple threads concurrently. * *

Synchronization transparency

* Other than the {@link #dispatchTasks() dispatchTasks()} method, methods of * this class are synchronization transparent. The * {@code dispatchTasks()} method is not synchronization transparent * only because it submits tasks to the underlying {@code Executor}. If * submitting tasks to the underlying executor is * synchronization transparent then even this method is * synchronization transparent. * * @see TaskExecutors#inOrderExecutor(TaskExecutor) * @see TaskExecutors#inOrderSyncExecutor() */ public final class TaskScheduler { /** * A convenience method effectively equivalent to * {@code new TaskScheduler(SyncTaskExecutor.getSimpleExecutor())}. * * @return a new task scheduler with a * {@code SyncTaskExecutor.getSimpleExecutor()} underlying executor. This * method never returns {@code null} and always returns a new instance. */ public static TaskScheduler newSyncScheduler() { return new TaskScheduler(Runnable::run); } private final Executor executor; private final AtomicReference dispatcherThread; // null means that noone is dispatching private final BlockingQueue toDispatch; /** * Creates a new task scheduler (without any task scheduled) with the given * backing executor. * * @param executor the executor to which tasks will be submitted to by the * {@link #dispatchTasks() dispatchTasks()} method. This argument cannot * be {@code null}. * * @throws NullPointerException thrown if the specified executor is * {@code null} */ public TaskScheduler(Executor executor) { Objects.requireNonNull(executor, "executor"); this.executor = executor; this.dispatcherThread = new AtomicReference<>(null); this.toDispatch = new LinkedBlockingQueue<>(); } /** * Schedules a single task for submitting to the executor specified at * construction time. *

* This method will not actually submit the task, to * do this call the {@link #dispatchTasks() dispatchTasks()} method. * * @param task the task to be scheduled for submitting to the executor * specified at construction time. This argument cannot be {@code null}. * * @throws NullPointerException thrown if the specified task is {@code null} */ public void scheduleTask(Runnable task) { Objects.requireNonNull(task, "task"); toDispatch.add(task); } /** * Schedules a list of tasks for submitting to the executor specified at * construction time. The tasks will be scheduled in the order defined by * the given list. *

* This method will not actually submit the task, to * do this call the {@link #dispatchTasks() dispatchTasks()} method. * * @param tasks the list of tasks to be scheduled for submitting to the * executor specified at construction time. This argument cannot be * {@code null} and cannot contain {@code null} elements. * * @throws NullPointerException thrown if the specified {@code tasks} is * {@code null} or contains {@code null} elements */ public void scheduleTasks(List tasks) { ExceptionHelper.checkNotNullElements(tasks, "tasks"); for (Runnable task: tasks) { scheduleTask(task); } } /** * Calling this method ensures that previously scheduled tasks will be * submitted to the executor specified at construction time. Note that tasks * may actually be submitted in different {@code dispatchTasks()} method * call but calling this method ensures that they will be submitted. *

* In case submitting a task causes an exception to be thrown, the * {@code dispatchTasks()} method actually submitting that task will * propagate that exception to the caller. Note however that a single * {@code dispatchTasks()} call can submit multiple tasks and if more than * one throws an exception, the exceptions after the first one will be * suppressed (See: {@link Throwable#addSuppressed(Throwable)}). */ public void dispatchTasks() { if (isCurrentThreadDispatching()) { // Tasks will be dispatched there. return; } Thread currentThread = Thread.currentThread(); Throwable toThrow = null; while (!toDispatch.isEmpty()) { if (dispatcherThread.compareAndSet(null, currentThread)) { try { Runnable task = toDispatch.poll(); if (task != null) { executor.execute(task); } } catch (Throwable ex) { if (toThrow == null) toThrow = ex; else toThrow.addSuppressed(ex); } finally { dispatcherThread.set(null); } } else { return; } } ExceptionHelper.rethrowIfNotNull(toThrow); } /** * Checks whether this method was invoked from a * {@link #dispatchTasks() dispatchTasks()} method call (i.e.: a task is * being submitted on the current calling thread). * * @return {@code true} if this method was invoked from a * {@code dispatchTasks()}, {@code false} otherwise */ public boolean isCurrentThreadDispatching() { return dispatcherThread.get() == Thread.currentThread(); } /** * Returns the string representation of this {@code TaskScheduler} in no * particular format. The string representation will contain the number of * tasks current waiting to be submitted. *

* This method is intended to be used for debugging only. * * @return the string representation of this object in no particular format. * This method never returns {@code null}. */ @Override public String toString() { return "TaskScheduler{Tasks to be executed: " + toDispatch.size() + '}'; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy