I had a similar problem and ended up creating a ForkListeningExecutorService
which wraps an ExecutorService
(see here). The wrapper sends events to a listener whenever a task is submitted to the executor or when it ends. You can then use the listener to pass whatever you want across the threads. This is how the Listener looks like:
/**
* Listener that will receive the events around task submission.
*
*/
public interface ExecutorServiceListener {
/**
* Will be called <b>before</b> any task is submitted to the service. This method will therefore run on the original "parent" thread.
*/
default void beforeTaskSubmission() {
// to be overridden by implementations
}
/**
* Will be called <b>after</b> a task is submitted to the service and <b>before</b> the actual execution starts. This method will therefore run on the new "child" thread.
*/
default void afterTaskSubmission() {
// to be overridden by implementations
}
/**
* Will be called <b>before</b> a submitted task ends no matter if an exception was thrown or not. This method will therefore run on the new "child" thread just before it's
* released.
*/
default void beforeTaskEnds() {
// to be overridden by implementations
}
}
So, instead of extending ThreadPoolExecutor
, you can simply wrap it (or whatever other ExecutorService
implementation) and pass the state within the listener like this:
public class ForkListeningExecutorServiceExample {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static void printThreadMessage(final String message) {
System.out.println(message
+ ", current thread: " + Thread.currentThread().getName()
+ ", value from threadLocal: " + threadLocal.get());
}
public static void main(final String[] args) throws Exception {
threadLocal.set("MY_STATE");
final ExecutorService executorService = new ForkListeningExecutorService(
Executors.newCachedThreadPool(),
new ExecutorServiceListener() {
private String valueToShare;
@Override
public void beforeTaskSubmission() {
valueToShare = threadLocal.get();
printThreadMessage("The task is about to be submitted");
}
@Override
public void afterTaskSubmission() {
threadLocal.set(valueToShare);
printThreadMessage("The task has been submitted and will start now");
}
@Override
public void beforeTaskEnds() {
threadLocal.set(null);
printThreadMessage("The task has finished and thread will be released now");
}
});
executorService.submit(() -> {
printThreadMessage("The task is running now");
}).get();
printThreadMessage("We are back on the main thread");
}
}
The output looks then like this:
The task is about to be submitted, current thread: main, value from threadLocal: MY_STATE
The task has been submitted and will start now, current thread: pool-1-thread-1, value from threadLocal: MY_STATE
The task is running now, current thread: pool-1-thread-1, value from threadLocal: MY_STATE
The task has finished and thread will be released now, current thread: pool-1-thread-1, value from threadLocal: null
We are back on the main thread, current thread: main, value from threadLocal: MY_STATE