Home > OS >  Execute beforeExecute(Thread t, Runnable r) in main thread rather than worker thread
Execute beforeExecute(Thread t, Runnable r) in main thread rather than worker thread

Time:12-08

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();
    }
}
  • Related