1. 程式人生 > >ThreadLocal遇上執行緒池存在的問題,用TransmittableThreadLocal解決

ThreadLocal遇上執行緒池存在的問題,用TransmittableThreadLocal解決

總結:請使用TransmittableThreadLocal

ThreadLocal是以執行緒為key的,而執行緒池裡面的執行緒是會被重新利用的,會導致ThreadLocal出現意料之外的事件。

比如可能會導致在SAAS中的串庫。

解決辦法如下:

@Slf4j
public class HiThreadPoolExecutor extends ThreadPoolExecutor {
    private Map<Integer, Thread> parentThreadMap = Collections.synchronizedMap(new LinkedHashMap<>());

    public HiThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public HiThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public HiThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public HiThreadPoolExecutor(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 void execute(Runnable command) {
        Thread parent = Thread.currentThread();
        // 呼叫execute方法時,儲存當前執行緒的副本
        parentThreadMap.put(command.hashCode(), new Thread(parent));
        super.execute(command);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        try {
            Field inheritableThreadLocals = Thread.class.getDeclaredField("inheritableThreadLocals");
            inheritableThreadLocals.setAccessible(true);

            // 取出execute方法中儲存的對應父執行緒副本,並將該副本的inheritableThreadLocals(ThreadLocal變數存放地)設定到即將執行的執行緒中
            Object value = inheritableThreadLocals.get(parentThreadMap.remove(r.hashCode()));
            if (value != null) inheritableThreadLocals.set(t, value);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            log.error("執行緒池錯誤" + HiThreadPoolExecutor.class.getName(), e);
        }
    }
}

原理是利用反射,線上程池提交任務時,將當前環境下的inheritableThreadLocals儲存,並在執行執行緒任務時,提取inheritableThreadLocals並設定到子執行緒中。

需要注意的是execute方法是在submit時呼叫的, 而beforeExecute方法是在執行執行緒任務之前呼叫的。