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

org.myire.concurrent.TaskRunner Maven / Gradle / Ivy

There is a newer version: 3.8
Show newest version
/*
 * Copyright 2006, 2009-2011, 2017 Peter Franzen. All rights reserved.
 *
 * Licensed under the Apache License v2.0: http://www.apache.org/licenses/LICENSE-2.0
 */
package org.myire.concurrent;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import static java.util.Objects.requireNonNull;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.ThreadSafe;


/**
 * A {@code TaskRunner} runs a {@code Runnable} in a separate thread, and provides means for
 * starting, stopping, and awaiting that thread.
 *

* The task execution thread will run until the {@code run()} method of the {@code Runnable} * returns. That thread can also be terminated explicitly from another thread by calling * {@link #stopTask()}. That method interrupts the task execution thread and then waits for it to * terminate by calling {@code Thread.join()}. The {@code Runnable} should therefore make sure that * it checks the current thread's interrupt status at regular intervals to detect if the task should * be explicitly terminated. *

* The task execution thread is created by a thread factory specified in the constructor. The * characteristics of this thread can further be tuned by overriding * {@link #prepareExecutionThread(Thread)}. That method is called from the thread that calls * {@link #startTask()} before the task execution thread is started. The default implementation of * {@link #prepareExecutionThread(Thread)} set the thread's {@code Thread.UncaughtExceptionHandler} * to {@link #onUncaughtException(Thread, Throwable)}, which by default does nothing. Subclasses may * want to override this method to take action when the task terminates abnormally. *

* This class is intended to be used for long-running tasks that may need to be stopped explicitly. * It is not intended to be used for repeated execution of many short-lived tasks, since a new * thread is created every time {@link #startTask()} is called. To execute many short-lived tasks, * one of the {@code java.util.concurrent.Executor} implementations is probably a better choice. *

* It is possible to restart a task that has been stopped by calling {@link #startTask()} again. * Whether or not the {@code Runnable} is reentrant is not the responsibility of the * {@code TaskRunner}, that must be guaranteed by the calling code. *

* Instances of this class are safe for use by multiple threads. * * @author Peter Franzen */ @ThreadSafe public class TaskRunner implements Awaitable { // The task to run. private final Runnable fTask; // The thread factory to create the task's execution thread with. private final ThreadFactory fThreadFactory; // The thread the task is running in (or did run in), null if the task hasn't ever been started. private volatile Thread fThread; // Monitor that must be held when starting and stopping the task execution thread. private final Object fThreadLock = new Object(); // CountDownLatch that is counted down to zero when the task execution thread has started. private volatile CountDownLatch fThreadStartLatch; /** * Create a new {@code TaskRunner}. The task's thread factory will be a * {@code UserThreadFactory} with the class name of {@code pTask} as base name for the thread. * * @param pTask The task to run. * * @throws NullPointerException if {@code pTask} is null. */ public TaskRunner(@Nonnull Runnable pTask) { this(pTask, new UserThreadFactory(pTask.getClass().getSimpleName(), false)); } /** * Create a new {@code TaskRunner}. * * @param pTask The task to run. * @param pThreadFactory The thread factory to create the task execution thread with. * * @throws NullPointerException if any of the parameters is null. */ public TaskRunner(@Nonnull Runnable pTask, @Nonnull ThreadFactory pThreadFactory) { fTask = requireNonNull(pTask); fThreadFactory = requireNonNull(pThreadFactory); } /** * Start running the task. This will create a new execution thread that will run the task passed * to the constructor by calling its {@code run()} method. The thread will execute until the * {@code run()} method returns or {@link #stopTask()} is called. *

* If the task already is running then this method does nothing and returns an {@code Awaitable} * that returns immediately from its {@code await} methods. * * @return An {@code Awaitable} that awaits the task execution thread to call the task's * {@code run()} method. */ @Nonnull public Awaitable startTask() { synchronized (fThreadLock) { if (!isRunning()) { // Not running, create and prepare the execution thread. fThread = fThreadFactory.newThread(this::runTask); prepareExecutionThread(fThread); // Create a CountdownLatch with count 1 that will reach 0 when the task execution // thread executes runTask(), and then start the thread. fThreadStartLatch = new CountDownLatch(1); fThread.start(); } return Awaitables.wrap(fThreadStartLatch); } } /** * Stop the task. This method will try to terminate the task execution thread gracefully by * setting its interrupted status. The thread calling this method will then wait for the task * execution thread to terminate before returning from this method. *

* If the task currently isn't running then this method does nothing. * * @throws InterruptedException if the calling thread is interrupted while waiting for the task * execution thread to terminate. */ public void stopTask() throws InterruptedException { synchronized (fThreadLock) { if (isRunning()) { // The task execution thread may be in a blocking operation and thereby may need to // be interrupted to exit the blocking call. Calling interrupt() if the thread isn't // blocked will only set the thread's interrupted status. fThread.interrupt(); // Wait for the task execution thread to terminate. fThread.join(); } } } /** * Check if the task is running. * * @return True if the task is running, false if not. */ public boolean isRunning() { Thread aThread = fThread; return aThread != null && aThread.isAlive(); } /** * Wait for the task execution thread to terminate. If the task currently is not running then * this method returns immediately. *

* If the task currently is running then the current thread becomes disabled for thread * scheduling purposes and lies dormant until one of two things happen: *

    *
  • The task execution thread terminates; or
  • *
  • Some other thread interrupts the current thread
  • *
* If the current thread has its interrupted status set on entry to this method then an * {@link InterruptedException} is thrown immediately and the current thread's interrupted * status is cleared. * * @throws InterruptedException if the current thread is interrupted while waiting. */ @Override public void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Thread aThread = fThread; if (aThread != null) aThread.join(); } /** * Wait for the task execution thread to terminate. If the task currently is not running then * this method returns immediately with the value {@code true}. *

* If the task currently is running then the current thread becomes disabled for thread * scheduling purposes and lies dormant until one of three things happen: *

    *
  • The task execution thread terminates; or
  • *
  • Some other thread interrupts the current thread; or
  • *
  • The specified waiting time elapses
  • *
* If the specified waiting time elapses before the task execution thread terminates then the * value {@code false} is returned. *

* If the current thread has its interrupted status set on entry to this method then an * {@link InterruptedException} is thrown immediately and the current thread's interrupted * status is cleared. * * @param pTimeout The maximum time to wait. If the time is less than or equal to zero, the * method will not wait at all. * @param pUnit The time unit of the {@code pTimeout} parameter. * * @return {@code true} if the task execution thread has terminated, {@code false} if the * waiting time elapsed before the thread terminated. * * @throws InterruptedException if the current thread is interrupted while waiting. * @throws NullPointerException if {@code pUnit} is null. */ @Override public boolean await(long pTimeout, @Nonnull TimeUnit pUnit) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Thread aThread = fThread; if (aThread != null) { pUnit.timedJoin(aThread, pTimeout); return !aThread.isAlive(); } else return true; } /** * Prepare the thread the task will run in. This method is called immediately before the thread * is started. *

* Subclasses can override this method to modify the thread's characteristics, such as giving * the thread another name than the one set by the thread factory specified in the constructor. *

* This base implementation sets the thread's {@link Thread.UncaughtExceptionHandler} to * {@link #onUncaughtException(Thread, Throwable)}. * * @param pThread A task execution thread about to be started. * * @throws NullPointerException if {@code pThread} is null. */ protected void prepareExecutionThread(@Nonnull Thread pThread) { pThread.setUncaughtExceptionHandler(this::onUncaughtException); } /** * Get notified that the task execution thread has thrown an uncaught exception and is about to * die. This implementation does nothing, subclasses may want to override to free up resources * or schedule a restart. * * @param pThread The thread where the exception occurred, normally the task execution * thread. * @param pThrowable The uncaught exception that caused the thread to die. * * @throws NullPointerException if any of the parameters is null. */ protected void onUncaughtException( @SuppressWarnings("unused") @Nonnull Thread pThread, @SuppressWarnings("unused") @Nonnull Throwable pThrowable) { // No-op. } /** * Run the task passed to the constructor. This method is called from the task execution thread. */ private void runTask() { // Signal that the task execution thread has started by counting down the latch returned by // startTask(). fThreadStartLatch.countDown(); // Run the task. fTask.run(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy