I want some way that beforeExecute() should be executed on the main thread before a thread execution everytime:
I have the following ThreadPoolExecutor:
public class ContextAwareThreadPool extends ThreadPoolExecutor {
public ContextAwareThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue<Runnable> workQueue, @NotNull ThreadFactory threadFactory, @NotNull RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
ThreadPoolContextHolder.setDatasourceForThread(t, DBContextHolder.getCurrentDb());
}
}
The biggest problem is that beforeExecute
is being called in the worker thread and not from the main thread(request thread). Thus DBContextHolder
which is a thread local in the main
thread is not set in the worker
thread.
Is there any sort of mechanism that a beforeExecute
callback is executed in the main
thread before the thread is actually executed ?
The design decision makes no sense to execute this in the worker thread. What if I want to set certain ThreadLocals in a Map and then retrieve it in the worker threads ?
CodePudding user response:
It is not practical to do what you want.
For the beforeExecute
to be executed on the main thread, the main thread would need to be sitting waiting on a queue of "callback objects". But that means that the main thread has to be doing nothing else.
The solution is to arrange that the information you need from the main thread's thread locals is copied to the Runnable
before it is submitted to the executor service. I've seen an example where they created a class that implemented Runnable
subclass that did the following:
In the constructor, capture the state of the thread locals. (The constructor is executed on the main thread.)
In the
run()
method copy the captured state to the current (worker) thread's thread locals.
It is not pleasant ... but it works.
The design decision makes no sense to execute this in the worker thread.
Actually, it makes perfect sense once you understand what would be involved in actually getting the callback to execute on the main thread. The Java language / execution model doesn't allow you to (magically) call a method on another thread's call stack.
CodePudding user response:
What if I want to set certain ThreadLocals in a Map and then retrieve it in the worker threads
You can consider wrapping runnable. Then set and clean up threadlocal
before and after run
method each time.
public class ContextAwareThreadPool extends ThreadPoolExecutor {
public ContextAwareThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(new RunnableWrapper(task, ThreadPoolContextHolder.get()));
}
// other submit methods...
static class RunnableWrapper implements Runnable {
private final Runnable target;
private final String dataSource;
public RunnableWrapper(Runnable target, String dataSource) {
this.dataSource = dataSource;
this.target = target;
}
@Override
public void run() {
ThreadPoolContextHolder.set(dataSource);
target.run();
ThreadPoolContextHolder.remove();
}
}
}
public class ThreadPoolContextHolder {
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void set(String s) {
threadLocal.set(s);
}
public static String get() {
return threadLocal.get();
}
public static void remove() {
threadLocal.remove();
}
}