package org.threadly.concurrent;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import org.threadly.util.ArgumentVerifier;
import org.threadly.util.ExceptionUtils;
/**
* A simple and light weight implementation of the {@link SchedulerService}. This is designed to
* be a lighter weight option to the {@link PriorityScheduler}, for when multiple threads are
* either not needed, or not desired.
*
* @since 2.0.0
*/
public class SingleThreadScheduler extends AbstractPriorityScheduler {
protected final SchedulerManager sManager;
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided. This defaults to using a daemon thread for the scheduler.
*/
public SingleThreadScheduler() {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided. This defaults to using a daemon thread for the scheduler.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
*/
public SingleThreadScheduler(TaskPriority defaultPriority, long maxWaitForLowPriorityInMs) {
this(defaultPriority, maxWaitForLowPriorityInMs, true);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
*
* @param daemonThread {@code true} if scheduler thread should be a daemon thread
*/
public SingleThreadScheduler(boolean daemonThread) {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, daemonThread);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
* @param daemonThread {@code true} if scheduler thread should be a daemon thread
*/
public SingleThreadScheduler(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs, boolean daemonThread) {
this(defaultPriority, maxWaitForLowPriorityInMs,
new ConfigurableThreadFactory(SingleThreadScheduler.class.getSimpleName() + "-",
true, daemonThread, Thread.NORM_PRIORITY, null, null));
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
*
* @param threadFactory factory to make thread for scheduler
*/
public SingleThreadScheduler(ThreadFactory threadFactory) {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, threadFactory);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
* @param threadFactory factory to make thread for scheduler
*/
public SingleThreadScheduler(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs, ThreadFactory threadFactory) {
this(defaultPriority,
new SchedulerManager(defaultPriority, maxWaitForLowPriorityInMs, threadFactory));
}
/**
* Constructs a new {@link SingleThreadScheduler}. This is for internal usage where different
* implementations of {@link SchedulerManager} need to be provided.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param SchedulerManager Scheduler manager to run against
*/
protected SingleThreadScheduler(TaskPriority defaultPriority, SchedulerManager schedulerManager) {
super(defaultPriority);
this.sManager = schedulerManager;
}
/**
* Gets the instance of the scheduler for this instance. The scheduler must be accessed from
* this function because it is lazily constructed and started. This call will verify the
* scheduler is running before it is returned
*
* @return instance of the internal scheduler
* @throws RejectedExecutionException thrown if the scheduler has been shutdown
*/
protected NoThreadScheduler getRunningScheduler() throws RejectedExecutionException {
sManager.startIfNotRunning();
if (sManager.hasBeenStopped()) {
throw new RejectedExecutionException("Thread pool shutdown");
}
return sManager.scheduler;
}
/**
* Stops the scheduler, constructing if necessary.
*
* @param stopImmediately if {@code true} after invocation no additional executions will occur
* @return if stopped immediately a list of Runnables that were in queue at stop will be returned
*/
private List<Runnable> shutdown(boolean stopImmediately) {
return sManager.stop(stopImmediately);
}
/**
* Stops any new tasks from being submitted to the pool. But allows all tasks which are
* submitted to execute, or scheduled (and have elapsed their delay time) to run. If recurring
* tasks are present they will also be unable to reschedule. This call will not block to wait
* for the shutdown of the scheduler to finish. If {@code shutdown()} or
* {@link #shutdownNow()} has already been called, this will have no effect.
* <p>
* If you wish to not want to run any queued tasks you should use {@link #shutdownNow()}.
*/
public void shutdown() {
shutdown(false);
}
/**
* Stops any new tasks from being submitted to the pool. If any tasks are waiting for execution
* they will be prevented from being run. If a task is currently running it will be allowed to
* finish (though this call will not block waiting for it to finish).
*
* @return returns a list of runnables which were waiting in the queue to be run at time of shutdown
*/
public List<Runnable> shutdownNow() {
return shutdown(true);
}
/**
* Block until the thread pool has shutdown and all threads have been stopped. If neither
* {@link #shutdown()} or {@link #shutdownNow()} is invoked, then this will block forever.
*
* @throws InterruptedException Thrown if blocking thread is interrupted waiting for shutdown
*/
public void awaitTermination() throws InterruptedException {
sManager.execThread.join();
}
/**
* Block until the thread pool has shutdown and all threads have been stopped. If neither
* {@link #shutdown()} or {@link #shutdownNow()} is invoked, then this will block until the
* timeout is reached.
*
* @param timeoutMillis time to block and wait for thread pool to shutdown
* @return {@code true} if the pool has shutdown, false if timeout was reached
* @throws InterruptedException Thrown if blocking thread is interrupted waiting for shutdown
*/
public boolean awaitTermination(long timeoutMillis) throws InterruptedException {
if (timeoutMillis > 0) {
sManager.execThread.join(timeoutMillis);
} else if (timeoutMillis == Long.MAX_VALUE) {
sManager.execThread.join();
}
return ! sManager.execThread.isAlive();
}
@Override
public boolean isShutdown() {
return sManager.hasBeenStopped();
}
/**
* One step beyond {@link #isShutdown()}, a {@code true} here indicates that not only has a
* shutdown on the pool been requested, but that the thread running tasks has completed.
*
* @return {@code true} if this scheduler is no longer processing any tasks
*/
public boolean isTerminated() {
return sManager.hasBeenStopped() && ! sManager.execThread.isAlive();
}
/**
* Start thread for execution if not already started. This can avoid the minor delay of the
* thread starting later. This will not block till the thread is running, instead letting it
* start in the background (usually fine).
*/
public void prestartExecutionThread() {
prestartExecutionThread(false);
}
/**
* Start thread for execution if not already started. This can avoid the minor delay of the
* thread starting later.
*
* @param blockTillStarted If {@code true} this will not return till the scheduler thread has started
*/
public void prestartExecutionThread(boolean blockTillStarted) {
if (sManager.startIfNotRunning()) {
sManager.scheduler.execute(DoNothingRunnable.instance());
if (blockTillStarted) {
while (! sManager.execThread.isAlive() && sManager.state.get() < 1) {
Thread.yield();
}
}
}
}
@Override
protected QueueManager getQueueManager() {
return sManager.scheduler.queueManager;
}
@Override
protected OneTimeTaskWrapper doSchedule(Runnable task, long delayInMillis, TaskPriority priority) {
return getRunningScheduler().doSchedule(task, delayInMillis, priority);
}
@Override
public void scheduleWithFixedDelay(Runnable task, long initialDelay, long recurringDelay,
TaskPriority priority) {
getRunningScheduler().scheduleWithFixedDelay(task, initialDelay, recurringDelay, priority);
}
@Override
public void scheduleAtFixedRate(Runnable task, long initialDelay, long period,
TaskPriority priority) {
getRunningScheduler().scheduleAtFixedRate(task, initialDelay, period, priority);
}
@Override
protected void finalize() {
// if being GC'ed, stop thread so that it also can be GC'ed
shutdown();
}
@Override
public int getActiveTaskCount() {
return sManager.scheduler.getActiveTaskCount();
}
/**
* This class contains the thread and instance of {@link NoThreadScheduler} that is used to
* provide single threaded scheduler implementation. The only implementation here is to contain
* those objects, and know how to start and stop the scheduler.
*
* @since 2.0.0
*/
protected static class SchedulerManager implements Runnable {
protected final NoThreadScheduler scheduler;
protected final AtomicInteger state = new AtomicInteger(-1); // -1 = new, 0 = started, 1 = stopping, 2 = stopped
protected final Thread execThread;
public SchedulerManager(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs, ThreadFactory threadFactory) {
this(new NoThreadScheduler(defaultPriority, maxWaitForLowPriorityInMs), threadFactory);
}
public SchedulerManager(NoThreadScheduler scheduler, ThreadFactory threadFactory) {
ArgumentVerifier.assertNotNull(threadFactory, "threadFactory");
this.scheduler = scheduler;
execThread = threadFactory.newThread(this);
if (execThread.isAlive()) {
throw new IllegalThreadStateException();
}
}
/**
* Checks if the scheduler has been requested to at least start the shutdown sequence. This
* may return {@code true} if the thread is still running, but should not accept more tasks
* unless this is returning {@code false}.
*
* @return {@code true} if the scheduler has been stopped
*/
public boolean hasBeenStopped() {
return state.get() > 0;
}
/**
* Starts the scheduler thread. If it has already been started this will throw an
* {@link IllegalStateException}.
*
* @return {@code true} if scheduler was started.
*/
public boolean startIfNotRunning() {
if (state.get() == -1 && state.compareAndSet(-1, 0)) {
execThread.start();
return true;
}
return false;
}
/**
* Call to stop the thread which is running tasks. If this has already been stopped this call
* will have no effect. Regardless if true or false is passed in, running tasks will NOT be
* Interrupted or stopped. True will only prevent ANY extra tasks from running, while a false
* will let tasks ready to run complete before shutting down.
*
* @param stopImmediately {@code false} if the scheduler should let ready tasks run,
* {@code true} stops scheduler immediately
* @return if {@code stopImmediately} is {@code true}, this will include tasks which were queued to run,
* otherwise will be an empty list
*/
public List<Runnable> stop(boolean stopImmediately) {
int stateVal = state.get();
while (stateVal < 1) {
if (state.compareAndSet(stateVal, 1)) {
// we finish the shutdown immediately if requested, or if it was never started
if (stopImmediately || stateVal == -1) {
return finishShutdown();
} else {
/* add to the end of the ready to execute queue a task which
* will finish the shutdown of the scheduler.
*/
scheduler.execute(new InternalRunnable() {
@Override
public void run() {
finishShutdown();
}
});
}
break;
} else {
stateVal = state.get();
}
}
return Collections.emptyList();
}
/**
* Finishes shutdown process, and clears any tasks that remain in the queue.
*
* @return a list of runnables which remained in the queue after shutdown
*/
private List<Runnable> finishShutdown() {
state.set(2);
scheduler.cancelTick();
return scheduler.clearTasks();
}
@Override
public void run() {
while (state.get() != 2) {
try {
scheduler.blockingTick(null);
} catch (InterruptedException e) {
// reset interrupted status
Thread.interrupted();
} catch (Throwable t) {
ExceptionUtils.handleException(t);
}
}
}
}
}