父子线程信息传递与TTL前面介绍了ThreadLocal使用与内存泄漏防范还从引用队列角度思考如何防范内存泄漏。这篇文章是自己在实际中用到了RAG检索与回答用自定义线程池而不是tomcat线程池防止tomcat线程池线程被占用导致无法处理其他请求。其中用到了跨线程的上下文传递就写个文章说一下。父子线程传递ThreadLocal问题ThreadLocal做上下文传递每个线程拿自己的用户信息互不干扰但如果子线程需要拿父线程的信息呢先做个测试public class Problem { private static final ThreadLocalString USER new ThreadLocal(); public static void main(String[] args) { USER.set(张三); System.out.println(主线程用户 USER.get()); new Thread(()-{ System.out.println(子线程用户 USER.get()); }).start(); } }结果根据前面的内容每个线程都有自己的ThreadLocal所以可以理解下面的结果主线程用户张三 子线程用户null怎么解决这个问题InheritableThreadLocalJDK提供了InheritableThreadLocal子线程能从父线程复制一份public class TestInheritable { private static final InheritableThreadLocalString USER new InheritableThreadLocal(); public static void main(String[] args) { USER.set(张三); System.out.println(主线程用户 USER.get()); new Thread(()-{ System.out.println(子线程用户 USER.get()); }).start(); } }运行结果主线程用户张三 子线程用户张三拿到了为什么可以拿到分析父子线程传递其实从第一篇文章就有体现在翻找第一篇文章的下面内容的时候就找到了这个InheritableThreadLocal第一篇的内容public class Thread implements Runnable { //省略。。。 /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals null; //省略。。。 }就在它的下方就有这个InheritableThreadLocal它也是Thread内部的进入Thread有俩个init方法看的是第二个private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { // 省略。。。 if (inheritThreadLocals parent.inheritableThreadLocals ! null) // 关键parent的inheritableThreadLocals中复制过来 this.inheritableThreadLocals ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stackSize stackSize; // 省略。。。 }进入createInheritedMapstatic ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }再进去ThreadLocalMapprivate ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable parentMap.table; // 拿到它爹的Entry数组 int len parentTable.length; setThreshold(len); table new Entry[len]; for (int j 0; j len; j) { //遍历这个数组 Entry e parentTable[j]; if (e ! null) { SuppressWarnings(unchecked) ThreadLocalObject key (ThreadLocalObject) e.get(); //这个key没被回收 if (key ! null) { // key不为null用childValue方法决定子线程的值 Object value key.childValue(e.value); Entry c new Entry(key, value); int h key.threadLocalHashCode (len - 1); while (table[h] ! null) h nextIndex(h, len); table[h] c; size; } } } }从上面看到return new ThreadLocalMap(parentMap)是把父线程的map放了进去进行遍历key没被回收的话就调用key.childValue方法得到新的value再给EntryEntry是ThreadLocalMap的内部类用于存储ThreadLocal的key和value。再进入childValue方法不过不是进入上面这个childValue方法而是进入InheritableThreadLocal的childValue方法。public class InheritableThreadLocalT extends ThreadLocalT { protected T childValue(T parentValue) { return parentValue; } //省略。。。 }这个方法是把传进来的e.value返回了回去一些细节ThreadLocalObject key (ThreadLocalObject) e.get(),e.get()的e是Entry类型的。前面第三个文章提到ntry的get是构造方法的时候通过super构造的而Entry又继承了WeakReference而WeakReference构造方法又调用了super,WeakReference又继承了ReferenceReference里有个private T referent;这个key层层super上去其实就是这个referent。整个过程没有new也就是说ThreadLocalObject key (ThreadLocalObject) e.get()中key和e.get()指的是堆里同一个对象。前面也提到key指的是ThreadLocal对象再往下看Object value key.childValue(e.value);而childValue在InheritableThreadLocal实现的是直接return了和上面key一样也是复制引用不复制对象再往下看Entry c new Entry(key, value)它new了也就是父的Entry和子的Entry不是指向同一个堆里的对象但是Entry里的key和value都是复制引用而没有复制对象所以Entry实际上也是浅拷贝。整个流程父子线程信息传递的时候父线程有个inheritableThreadLocals子线程Thread在init的时候拷贝父线程的ThreadLocalMap信息浅拷贝这样就获取了父线程的ThreadLocal的Entry每个Entry都有key和valueInheritableThreadLocal的问题从上面看出它获取父线程的值的时候是在自己这个线程创建的时候调用init方法也就是创建时复制提交时不复制意思就是线程池里复用的线程想拿父线程的信息只能在初始化的时候拿一次之后父线程ThreadLocalMap里新增了其他Entry子线程它拿不到了public class InheritableThreadLocalProblem { // 说明InheritableThreadLocal的问题 private static final InheritableThreadLocalString USER new InheritableThreadLocal(); public static void main(String[] args) throws InterruptedException { // 用线程池做线程复用 ExecutorService pool Executors.newFixedThreadPool(2); // 主线程set值 USER.set(张三); //提交到线程池线程池创建核心线程创建线程会获取父线程信息 pool.submit(()-{ System.out.println(线程池用户为 USER.get()); }); //主线程更新 USER.set(李四); pool.submit(()-{ System.out.println(线程池用户为 USER.get()); }); //线程池执行任务是异步的防止main直接没了导致线程池任务没跑就关掉 Thread.sleep(1000); pool.shutdown(); } }执行结果线程池用户为张三 线程池用户为李四输出是正确的因为核心线程弄了俩set之后pool里调用的时候创建了新线程没有复用。多执行几次这次三次set同时把线程池哪个线程执行的也打印出来。public class InheritableThreadLocalProblem { // 说明InheritableThreadLocal的问题 private static final InheritableThreadLocalString USER new InheritableThreadLocal(); public static void main(String[] args) throws InterruptedException { // 用线程池做线程复用 ExecutorService pool Executors.newFixedThreadPool(2); // 主线程set值 USER.set(张三); //提交到线程池线程池创建核心线程创建线程会获取父线程信息 pool.submit(()-{ System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); //主线程更新 USER.set(李四); pool.submit(()-{ System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); USER.set(王五); pool.submit(()-{ System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); //线程池执行任务是异步的防止main直接没了导致线程池任务没跑就关掉 Thread.sleep(1000); pool.shutdown(); } }执行结果符合预期主线程更新了用户信息线程池却并没有进行更新。pool-1-thread-1 用户张三 pool-1-thread-2 用户李四 pool-1-thread-1 用户张三解决方案为什么解决场景Tomcat线程池复用核心线程执行任务的时候调用线程池执行耗时长的任务而线程池又需要Tomcat当前线程的上下文信息这就需要上下文信息支持跨线程传递TransmittableThreadLocalTTLTransmittableThreadLocal专门解决线程池场景的传递问题先导入依赖dependency groupIdcom.alibaba/groupId artifactIdtransmittable-thread-local/artifactId version2.14.5/version /dependency测试public class TTLTest { private static final ThreadLocalString USER new TransmittableThreadLocal(); public static void main(String[] args) throws InterruptedException { // 用线程池做线程复用 ExecutorService pool Executors.newFixedThreadPool(2); //装饰线程池用ttl执行任务 ExecutorService ttlPool TtlExecutors.getTtlExecutorService(pool); // 主线程set值 USER.set(张三); //提交到线程池 ttlPool.submit(() - { System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); //主线程更新 USER.set(李四); ttlPool.submit(() - { System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); USER.set(王五); ttlPool.submit(() - { System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); //线程池执行任务是异步的防止main直接没了导致线程池任务没跑就关掉 Thread.sleep(1000); pool.shutdown(); } }结果pool-1-thread-1 用户张三 pool-1-thread-2 用户李四 pool-1-thread-1 用户王五