package org.threadly.concurrent;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import org.threadly.util.ExceptionHandler;
import org.threadly.util.ExceptionUtils;
/**
* Implementation of {@link ThreadFactory} which is configurable for the most common use cases.
* Specifically, it allows you several options for how to prefix the name of threads, if returned
* threads should be daemon or not, what their priority should be, and if an
* {@link UncaughtExceptionHandler} should be provided to new threads. You can construct this
* with no arguments, and it's behavior will match that of
* {@code Executors.defaultThreadFactory()}.
*
* @since 2.3.0
*/
public class ConfigurableThreadFactory implements ThreadFactory {
protected static final boolean DEFAULT_NEW_THREADS_DAEMON = false;
private static final AtomicInteger NEXT_POOL_NUMBER = new AtomicInteger(1);
protected final ThreadGroup group;
protected final String threadNamePrefix;
protected final boolean useDaemonThreads;
protected final int threadPriority;
protected final UncaughtExceptionHandler defaultUncaughtExceptionHandler;
protected final ExceptionHandler defaultThreadlyExceptionHandler;
private final AtomicInteger nextThreadNumber;
/**
* Constructs a new {@link ConfigurableThreadFactory} with the default parameters. Threads
* produced from this should behave exactly like Executors.defaultThreadFactory(), except the
* pool number provided in the thread name will be respective to the ones created from other
* {@link ConfigurableThreadFactory} instances.
*/
public ConfigurableThreadFactory() {
this(null, true, DEFAULT_NEW_THREADS_DAEMON, Thread.NORM_PRIORITY, null, null);
}
/**
* Constructs a new {@link ConfigurableThreadFactory} specifying the prefix for the name of
* newly created threads.
* <p>
* If specified with {@code true} for {@code appendPoolIdToPrefix} it will append a unique
* "pool" id to the prefix, giving it the format of
* {@code threadNamePrefix + UNIQUE_POOL_ID + "-thread-"}. If {@code appendPoolIdToPrefix} is
* specified as {@code false}, only a unique thread id will be appended to the prefix. In
* either case, the produced threads name will be appended with a unique thread id for the
* factory instance.
*
* @param threadNamePrefix prefix for all threads created
* @param appendPoolIdToPrefix {@code true} to append a unique pool id to the thread prefix
*/
public ConfigurableThreadFactory(String threadNamePrefix, boolean appendPoolIdToPrefix) {
this(threadNamePrefix, appendPoolIdToPrefix,
DEFAULT_NEW_THREADS_DAEMON, Thread.NORM_PRIORITY, null, null);
}
/**
* Constructs a new {@link ConfigurableThreadFactory} specifying the behavior for if threads
* should be daemon or not.
*
* @param useDaemonThreads {@code true} if produced threads should be daemon threads
*/
public ConfigurableThreadFactory(boolean useDaemonThreads) {
this(null, true, useDaemonThreads, Thread.NORM_PRIORITY, null, null);
}
/**
* Constructs a new {@link ConfigurableThreadFactory} specifying the priority for produced
* threads.
* <p>
* If the priority is below or above the max available thread priority, this will be adjusted to
* the limit of the system.
*
* @param threadPriority Priority for newly created threads
*/
public ConfigurableThreadFactory(int threadPriority) {
this(null, true, DEFAULT_NEW_THREADS_DAEMON, threadPriority, null, null);
}
/**
* Constructs a new {@link ConfigurableThreadFactory} specifying an
* {@link UncaughtExceptionHandler} that will be provided to all newly created threads.
*
* @param defaultUncaughtExceptionHandler {@link UncaughtExceptionHandler} to provide to newly created threads
*/
public ConfigurableThreadFactory(UncaughtExceptionHandler defaultUncaughtExceptionHandler) {
this(null, true, DEFAULT_NEW_THREADS_DAEMON,
Thread.NORM_PRIORITY, defaultUncaughtExceptionHandler, null);
}
/**
* Constructs a new {@link ConfigurableThreadFactory} specifying an {@link ExceptionHandler}
* that will be provided to all newly created threads.
*
* @param defaultThreadlyExceptionHandler {@link ExceptionHandler} to provide to newly created threads
*/
public ConfigurableThreadFactory(ExceptionHandler defaultThreadlyExceptionHandler) {
this(null, true, DEFAULT_NEW_THREADS_DAEMON,
Thread.NORM_PRIORITY, null, defaultThreadlyExceptionHandler);
}
/**
* Constructs a new {@link ConfigurableThreadFactory} allowing you to provide specific values
* for everything which this class allows to be configured. You must use this constructor if
* you need to adjust two or more values.
* <p>
* If specified with {@code true} for {@code appendPoolIdToPrefix} it will append a unique
* "pool" id to the prefix, giving it the format of
* {@code threadNamePrefix + UNIQUE_POOL_ID + "-thread-"}. If {@code appendPoolIdToPrefix} is
* specified as {@code false}, only a unique thread id will be appended to the prefix. In
* either case, the produced threads name will be appended with a unique thread id for the
* factory instance.
* <p>
* If the priority is below or above the max available thread priority, this will be adjusted to
* the limit of the system.
*
* @param threadNamePrefix prefix for all threads created, {@code null} to match default
* @param appendPoolIdToPrefix {@code true} to append a unique pool id to the thread prefix,
* {@code true} to match default
* @param useDaemonThreads true if produced threads should be daemon threads, false to match default
* @param threadPriority Priority for newly created threads, {@code Thread.NORM_PRIORITY} to match default
* @param uncaughtExceptionHandler UncaughtExceptionHandler to provide to newly created threads,
* {@code null} to match default
* @param defaultThreadlyExceptionHandler {@link ExceptionHandler} to provide to newly created threads
*/
public ConfigurableThreadFactory(String threadNamePrefix, boolean appendPoolIdToPrefix,
boolean useDaemonThreads, int threadPriority,
UncaughtExceptionHandler uncaughtExceptionHandler,
ExceptionHandler defaultThreadlyExceptionHandler) {
if (threadPriority > Thread.MAX_PRIORITY) {
threadPriority = Thread.MAX_PRIORITY;
} else if (threadPriority < Thread.MIN_PRIORITY) {
threadPriority = Thread.MIN_PRIORITY;
}
if (threadNamePrefix == null) {
threadNamePrefix = "pool-";
}
if (appendPoolIdToPrefix) {
threadNamePrefix += NEXT_POOL_NUMBER.getAndIncrement() + "-thread-";
}
SecurityManager s = System.getSecurityManager();
if (s != null) {
this.group = s.getThreadGroup();
} else {
this.group = Thread.currentThread().getThreadGroup();
}
this.threadNamePrefix = threadNamePrefix;
this.useDaemonThreads = useDaemonThreads;
this.threadPriority = threadPriority;
this.defaultUncaughtExceptionHandler = uncaughtExceptionHandler;
this.defaultThreadlyExceptionHandler = defaultThreadlyExceptionHandler;
this.nextThreadNumber = new AtomicInteger(1);
}
@Override
public Thread newThread(Runnable r) {
if (defaultThreadlyExceptionHandler != null) {
r = new ExceptionHandlerSettingRunnable(r);
}
Thread t = new Thread(group, r,
threadNamePrefix + nextThreadNumber.getAndIncrement());
if (t.isDaemon() != useDaemonThreads) {
t.setDaemon(useDaemonThreads);
}
if (t.getPriority() != threadPriority) {
t.setPriority(threadPriority);
}
if (defaultUncaughtExceptionHandler != null) {
t.setUncaughtExceptionHandler(defaultUncaughtExceptionHandler);
}
return t;
}
/**
* Because the {@link ExceptionHandler} can not be set before the thread is started. We must
* wrap it in this implementation to set the handler before the runnable actually starts.
*
* @since 2.4.0
*/
protected class ExceptionHandlerSettingRunnable implements Runnable {
private final Runnable toRun;
protected ExceptionHandlerSettingRunnable(Runnable r) {
toRun = r;
}
@Override
public void run() {
ExceptionUtils.setThreadExceptionHandler(defaultThreadlyExceptionHandler);
toRun.run();
}
}
}