ThreadLocal遇上執行緒池存在的問題,用TransmittableThreadLocal解決
阿新 • • 發佈:2019-01-05
總結:請使用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
方法是在執行執行緒任務之前呼叫的。