com.senzing.util.WorkerThreadPool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of senzing-commons Show documentation
Show all versions of senzing-commons Show documentation
Utility classes and functions common to multiple Senzing projects.
The newest version!
package com.senzing.util;
import java.util.*;
/**
* Provides a simple worker thread that can be pooled and can execute a task
* within the thread.
*/
public class WorkerThreadPool {
/**
* The default base name used as a prefix for threads created in the pool if
* none is specified.
*/
public static final String DEFAULT_BASE_NAME = "WorkerThread";
/**
* The list of available {@link WorkerThread} instances.
*/
private List available;
/**
* The list of all {@link WorkerThread} instances whether available or not.
*/
private List allThreads;
/**
* Flag indicating if the pool has been marked closed.
*/
private boolean closed;
/**
* The {@link AccessToken} indicating if this instance is paused. This is
* null
if not paused.
*/
private AccessToken pauseToken;
/**
* Constructs with the specified number of threads in the pool.
*
* @param size The number of threads to create in the pool.
*/
public WorkerThreadPool(int size)
{
this(DEFAULT_BASE_NAME, size);
}
/**
* Constructs with the specified thread base name and the number of threads
* to create.
*
* @param baseName The base name to use as a prefix when naming the
* worker threads in the pool.
*
* @param size The number of worker threads to create.
*/
public WorkerThreadPool(String baseName, int size)
{
if (size < 1) {
throw new IllegalArgumentException(
"The thread-pool size must be a positive number. size=[ " + size
+ " ]");
}
this.available = new LinkedList<>();
this.allThreads = new LinkedList<>();
this.closed = false;
// if baseName ends with "-" then strip it off since we will add it back
if (baseName.endsWith("-")) {
baseName = baseName.substring(0, baseName.length() - 1);
}
int identityHashCode = System.identityHashCode(this);
for (int index = 0; index < size; index++) {
WorkerThread wt = new WorkerThread();
wt.setName(baseName + "-" + identityHashCode + "-" + index);
this.available.add(wt);
this.allThreads.add(wt);
wt.start();
}
this.allThreads = Collections.unmodifiableList(this.allThreads);
}
/**
* Returns the size of the worker thread pool.
*
* @return The size of the worker thread pool.
*/
public int size() {
return this.allThreads.size();
}
/**
* Checks if this pool has been closed. Once closed, the pool can no longer
* be used to execute any further tasks.
*
* @return true
if closed and false
if not.
*/
public boolean isClosed() {
synchronized (this.available) {
return this.closed;
}
}
/**
* Closes this pool so no further tasks can be executed against it.
*
* @param join Whether or not to join against each thread and wait for each
* thread to complete.
*/
public void close(boolean join)
{
// mark this pool as closed and notify
synchronized (this.available) {
if (this.closed) return;
this.closed = true;
this.available.notifyAll();
}
// mark all the threads complete
for (WorkerThread thread: this.allThreads) {
thread.markComplete();
}
// check if we are joining
if (join) {
// loop through the threads and join
for (WorkerThread thread: this.allThreads) {
try {
// join against this thread
thread.join();
} catch (InterruptedException ignore) {
// ignore the exception
}
}
}
}
/**
* Executes the specified task on the first available worker thread.
*
* @param task The {@link Task} to execute.
*
* @return The result from executing the {@link Task}.
*
* @throws E If the specified {@link Task#execute()} method throws
* an exception.
*
* @param The type returned by the worker thread.
* @param The type of the exception thrown by the worker thread if a
* failure occurs.
*/
public T execute(Task task) throws E
{
WorkerThread thread = null;
synchronized (this.available) {
// check if already closed
if (this.isClosed()) {
throw new IllegalStateException(
"This WorkerThreadPool has already been marked as closed and the "
+ "threads have been shutdown.");
}
// wait for an available worker thread
while (this.available.size() == 0 || this.pauseToken != null) {
try {
this.available.wait(2000L);
} catch (InterruptedException ignore) {
// do nothing
}
}
thread = this.available.remove(0);
}
// execute the task on the thread and get the result
try {
// execute the task and return the result
return thread.execute(task);
} finally {
if (thread != null) {
// synchronize on the available pool
synchronized (this.available) {
// if not complete then return it to the pool and set thread to null
if (!this.closed) {
this.available.add(thread);
this.available.notifyAll();
// set the thread to null to indicate that it was returned
// to the thread pool
thread = null;
}
}
// if the thread was not returned to the pool then we need to
// mark it complete so it cleans up
if (thread != null) {
thread.markComplete();
}
}
}
}
/**
* Checks if this {@link WorkerThreadPool} has been paused.
*
* @return true
if this instance has paused, otherwise false
*/
public boolean isPaused() {
return (this.pauseToken != null);
}
/**
* Pauses access to this {@link WorkerThreadPool} by preventing others from
* obtaining a thread until the {@link #resume(AccessToken)} method is called
* (which should occur in a "finally" block typically). If there are
* outstanding workers executing tasks on threads then this method waits until
* they are complete.
*
* @return The {@link AccessToken} to use for {@linkplain
* #resume(AccessToken) unpausing}, or null
if already
* paused when this is called.
*/
public AccessToken pause() {
synchronized (this.available) {
// check if already paused
if (this.isPaused()) return null;
// mark this instance as paused
this.pauseToken = new AccessToken();
// wait until all threads are available
while (this.available.size() < this.allThreads.size()) {
try {
this.available.wait(2000L);
} catch (InterruptedException ignore) {
// do nothing
}
}
// return the pause token
return this.pauseToken;
}
}
/**
* Unpauses the pool and allows tasks to be executed on the available
* threads if the specified {@link AccessToken} is valid. If the specified
* parameter is null
then this method does nothing and if it is the
* wrong {@link AccessToken} then an {@link IllegalArgumentException} is
* thrown.
*
* @param accessToken The {@link AccessToken} to use to verify unpausing the
* {@link WorkerThreadPool}, or null
if the
* caller was not the one who paused this instance.
*
* @return true
if the instance was unpaused, otherwise
* false
.
*
* @throws IllegalStateException If this instance is not in a paused
* state and the specified parameter is not
* null
.
*
* @throws IllegalArgumentException If the specified parameter is not the
* correct {@link AccessToken}.
*/
public boolean resume(AccessToken accessToken) {
if (accessToken == null) return false;
synchronized (this.available) {
if (accessToken != null && this.pauseToken == null) {
throw new IllegalStateException(
"This WorkerThreadPool is NOT currently in a paused state. Cannot "
+ "resume.");
}
if (accessToken != null && this.pauseToken != accessToken) {
throw new IllegalArgumentException(
"The specifeid access token is not valid for unpausing this "
+ "WorkerThreadPool instance.");
}
// resume
this.pauseToken = null;
this.available.notifyAll();
return true;
}
}
/**
* The interface describing the tasks that can be performed by the
* WorkerThread.
*
* @param The type returned by the worker thread.
* @param The type of the exception thrown by the worker thread if a
* failure occurs.
*/
public interface Task {
T execute() throws E;
}
/**
* Internal worker thread class.
*/
private class WorkerThread extends Thread {
/**
* Flag to indicate if the worker thread is complete.
*/
private boolean complete;
/**
* The task to execute.
*/
private Task task = null;
/**
* The returned object from the last task.
*/
private Object result = null;
/**
* The failure from the last task.
*/
private Exception failure = null;
/**
* Flag indicating if this thread is busy executing a task.
*/
private boolean busy = false;
/**
* Default constructor.
*/
private WorkerThread() {
this.complete = false;
}
/**
* Resets the thread so it is ready to execute the next task.
*/
private void reset() {
this.task = null;
this.result = null;
this.failure = null;
this.busy = false;
}
/**
* Checks if the thread has been marked complete and should stop
* processing tasks.
*
* @return true
if this instance has been marked complete,
* otherwise false
.
*/
private synchronized boolean isComplete() {
return this.complete;
}
/**
* Marks this thread complete so it stops waiting for new tasks to execute.
*/
private synchronized void markComplete() {
this.complete = true;
this.notifyAll();
}
/**
* Checks if this thread is currently busy executing a task.
*
* @return true
if this thread is currently busy executing a
* task, otherwise false
.
*/
private synchronized boolean isBusy() {
return this.busy;
}
/**
* Executes the specified task and returns the result from that task or
* throws the exception generated by the task.
*
* @param task The task to execute.
*
* @return The object return returned by the specified task.
*
* @throws Exception If the specified task throws an exception.
*/
@SuppressWarnings("unchecked")
private synchronized T execute(Task task)
throws E
{
try {
if (this.isBusy()) {
throw new IllegalStateException("Already busy with another task.");
}
// flag as busy
this.busy = true;
// set the runnable
this.task = task;
// notify
this.notifyAll();
// wait for completion (releasing the lock)
while (this.task != null) {
try {
this.wait(2000L);
} catch (InterruptedException ignore) {
// ignore the exception
}
}
// check for a failure
if (this.failure != null) {
E e = (E) this.failure;
this.reset();
throw e;
}
// get the result
T result = (T) this.result;
// reset the worker thread
this.reset();
// return the result
return result;
} finally {
this.reset();
}
}
/**
* Implement the run method to wait for the next task and execute it.
* This continues until this thread is marked complete.
*/
public void run()
{
synchronized (this) {
// loop while not complete
while (!this.isComplete()) {
// loop while not complete and no task
while (this.task == null && !this.isComplete()) {
try {
this.wait(10000L);
} catch (InterruptedException ignore) {
// do nothing
}
}
// check if we have a task to do
if (this.task != null) {
try {
// execute the task and record the result
this.result = this.task.execute();
} catch (Exception e) {
// record any failure for the task
this.failure = e;
}
// clear the task
this.task = null;
// make sure to notify when done
this.notifyAll();
}
}
}
}
}
public static void main(String[] args) {
WorkerThreadPool pool = new WorkerThreadPool(4);
for (int index = 0; index < args.length; index++) {
final String arg = args[index];
try {
Object result = pool.execute(() -> {
String threadName = Thread.currentThread().getName();
String message = threadName + ": " + arg;
if (arg.startsWith("ERROR")) {
throw new RuntimeException(message);
}
return message;
});
System.out.println(result);
} catch (Exception failure) {
failure.printStackTrace();
}
}
System.out.println();
System.out.println("JOINING AGAINST THE POOL....");
pool.close(true);
System.out.println("JOINED AGAINST THE POOL.");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy