├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── xjjdog │ └── passthrough │ ├── PassThroughFactory.java │ ├── hystrix │ ├── CommonHystrixConcurrencyStrategy.java │ └── HystrixFactory.java │ ├── mdc │ ├── MDCCallable.java │ └── MDCRunnable.java │ └── threadlocal │ ├── ThreadLocal.java │ ├── ThreadLocalCallable.java │ ├── ThreadLocalHandler.java │ ├── ThreadLocalRunnable.java │ └── Transmissible.java └── test └── java └── com └── github └── xjjdog └── passthrough └── threadlocal ├── SimpleThreadLocalNotOkTest.java ├── SimpleThreadLocalOkTest.java └── ThreadLocalTest.java /README.md: -------------------------------------------------------------------------------- 1 | #  你的也是我的。3例ko多线程,局部变量透传 2 | >原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。 3 | 4 | java中的threadlocal,是绑定在线程上的。你在一个线程中set的值,在另外一个线程是拿不到的。如果在threadlocal的平行线程中,创建了新的子线程,那么这里面的值是无法传递、共享的(先想清楚为什么再往下看)。这就是透传问题。 5 | 6 | 值在线程之间的透传,你可以认为是一个bug,这些问题一般会比较隐蔽,但问题暴露的时候脾气却比较火爆,让人手忙脚乱,怀疑人生。 7 | 8 | 作为代码的掌舵者,我们必然不能忍受这种问题的蹂躏。本篇文章适合细看,我们拿出3个例子,通过编码手段说明解决此类bug的通用方式,希望能达到举一反三的效果。对于搞基础架构的同学,是必备知识点。 9 | 10 | 1、普通线程的ThreadLocal透传问题 11 | 2、sl4j MDC组件中ThreadLocal透传问题 12 | 3、Hystrix组件的透传问题 13 | 14 | 由于涉及代码比较多,xjjdog将这三个例子的代码,放在了github上,想深入研究,可以下载下来debug一下。 15 | ``` 16 | https://github.com/xjjdog/example-pass-through 17 | ``` 18 | 19 | # 一、问题简单演示 20 | 21 | 为了有个比较直观的认识,下面展示一段异常代码。 22 | ![](media/15661756105776/15661977979729.jpg) 23 | 以上代码在主线程设置了一个简单的threadlocal变量,然后在自线程中想要取出它的值。执行后发现,程序的输出是:`null`。 24 | 25 | 程序的输出和我们的期望产生了明显的差异。其实,将**ThreadLocal** 换成**InheritableThreadLocal** 就ok了。不要高兴太早,对于使用线程池的情况,由于会缓存线程,线程是缓存起来反复使用的。这时父子线程关系的上下文传递,已经没有意义。 26 | 27 | # 二、解决线程池透传问题 28 | 29 | 所以,线程池InheritableThreadLocal进行提交,获取的值,有可能是前一个任务执行后留下的,是错误的。使用只有在任务执行的时候进行传递,才是正常的功能。 30 | 31 | 上面的问题,transmittable-thread-local项目,已经很好的解决,并提供了java-agent的方式支持。 32 | 33 | 我们这里从最小集合的源码层面,来看一下其中的内容。首先,我们看一下ThreadLocal的结构。 34 | ![](media/15661756105776/15662066178206.jpg) 35 | ThreadLocal其实是作为一个Map中的key而存在的,这个Map就是ThreadLocalMap,它以私有变量的形式,存在于Thread类中。拿上图为例,如果我创建了一个ThreadLocal,然后调用set方法,它会首先找到当前的thread,然后找到threadLocals,最后把自己作为key,存放在这个map里。 36 | ``` 37 | hread t = Thread.currentThread(); 38 | ThreadLocalMap map = getMap(t); 39 | map.set(this, value); 40 | ``` 41 | 42 | 要能够完成多线程的协调工作,必须提供全套的多线程工具。包括但不限于: 43 | 44 | **1、定义注解,以及被注解修饰的ThreadLocal类** 45 | ![](media/15661756105776/15662008169736.jpg) 46 | 定义新的ThreadLocal类,以便在赋值的时候,能够根据注解进行拦截和过滤。这就要求,在定义ThreadLocal的时候,要使用我们提供的ThreadLocal类,而不是jdk提供的那两个。 47 | 48 | **2、进行父子线程之间的数据拷贝** 49 | 在线程池提交任务之前,我们需要有个地方,将父进程的ThreadLocal内容,暂存一下。 50 | ![](media/15661756105776/15662073449760.jpg) 51 | 由于很多变量都是private的,需要根据反射进行操作。根据上面提供的ThreadLocal类的结构,我们需要直接操作其中的变量table(这也是为什么jdk不能随便改变变量名的原因)。 52 | 53 | 将父线程相关的变量暂存之后,就可以在使用的时候,通过主动设值和清理,完成变量拷贝。 54 | 55 | **3、提供专用的Callable或者Runnable** 56 | 那么这些数据是如何组装起来的呢?还是靠我们的任务载体类。 57 | 线程池提交线程,一般是通过Callable或者Runnable,以Runnable为例,我们看一下这个调用关系。 58 | 59 | 以下类采用了委托模式。 60 | ![](media/15661756105776/15662081632496.jpg) 61 | 62 | 这样,只要在提交任务的时候,使用了我们自定义的Runnable;同时,使用了自定义的ThreadLocal,就能够正常完成透传。 63 | 64 | # 三、解决MDC透传问题 65 | 66 | sl4j MDC机制非常好,通常用于保存线程本地的“诊断数据”然后有日志组件打印,其内部时基于threadLocal实现;不过这就有一些问题,主线程中设置的MDC数据,在其子线程(多线程池)中是无法获取的,下面就来介绍如何解决这个问题。 67 | 68 | >MDC ( Mapped Diagnostic Contexts ),它是一个线程安全的存放诊断日志的容器。通常,会在处理请求前将请求的唯一标示放到MDC容器中,比如sessionId。这个唯一标示会随着日志一起输出。配置文件可以使用占位符进行变量替换。 69 | 70 | 类似于上面介绍的方式,我们需要提供专用的Callable和Runnable。另外,为了能够同时支持MDC和普通线程,这两个类采用装饰器模式,进行功能追加。就单个类来说,对外的展现依然是委托模式。 71 | ![](media/15661756105776/15662086854501.jpg) 72 | 同样的思路,同样的模式。不一样的是,父线程的信息暂存,我们直接使用MDC的内部方法,并在任务的执行前后,进行相应操作。 73 | 74 | # 四、解决Hystrix透传问题 75 | 76 | 同样的问题,在Netflix公司的熔断组件Hystrix中,依然存在。Hystrix线程池模式下,透传ThreadLocal需要进行改造,它本身是无法完成这个功能的。 77 | 78 | 但是Hystrix策略无法简单通过yml文件方式配置。我们参考Spring Cloud中对此策略的扩展方式,开发自己的策略。需要继承HystrixConcurrentStrategy。 79 | 80 | 构造代码还是较长的,可以查看github项目。但有一个地方需要说明。 81 | ![](media/15661756105776/15662093008407.jpg) 82 | 我们使用装饰器模式,对代码进行了层层嵌套,同时将多线程透传功能、MDC传递功能给追加了进来。这样,我们的这个类,就同时在以上三个环境中拥有了透传功能。 83 | 84 | # End 85 | 同样的思路,可以用在其他组件上。比如我们在多篇调用链的文章里,提到的trace信息在多线程环境下的传递。 86 | 87 | 一般就是在当前线程暂存数据,然后在提交任务时进行包装。值得注意的是,这种方式侵入性还是比较大的,适合封装在通用的基础工具包中。你要是在业务中这么用,大概率会被骂死。 88 | 89 | 那可如何是好。 90 | 91 | ThreadLocal会引发很多棘手的bug,造成代码污染。在使用之前,一定要确保你确实需要使用它。比如你在SimpleDateFormat类上用了线程局部变量,可以将它替换成DateTimeFormatter。 92 | 93 | 我们不善于解决问题,我们只善于解决容易出问题的类。 94 | 95 | >作者简介:**小姐姐味道** (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,​进一步交流。​ 96 | 97 | 近期热门文章​ 98 | 99 | [《必看!java后端,亮剑诛仙》](https://mp.weixin.qq.com/s/Cuv0SyjzasDKC0wIQxrgaw) 100 | 后端技术索引,中肯火爆 101 | 102 | [《Linux上,最常用的一批命令解析(10年精选)》](https://mp.weixin.qq.com/s/9RbTGQ4k4s92mrSf2xJ5TQ) 103 | CSDN发布首日,1k赞。点赞率1/8。 104 | 105 | [《这次要是讲不明白Spring Cloud核心组件,那我就白编这故事了》](https://mp.weixin.qq.com/s/hjYAddJEqgg3ZWTJnPTD9g) 106 | 用故事讲解核心组件,包你满意 107 | 108 | [《Linux生产环境上,最常用的一套“Sed“技巧》](https://mp.weixin.qq.com/s/wP9_wvoTARRrlszsOmvMgQ) 109 | 最常用系列Sed篇,简单易懂。Vim篇更加易懂。 110 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.xjjdog 8 | example-pass-through 9 | 1.0.0 10 | 11 | 12 | ch.qos.logback 13 | logback-access 14 | 1.2.3 15 | 16 | 17 | ch.qos.logback 18 | logback-classic 19 | 1.2.3 20 | 21 | 22 | 23 | ch.qos.logback 24 | logback-core 25 | 1.2.3 26 | 27 | 28 | 29 | com.netflix.hystrix 30 | hystrix-core 31 | 1.5.12 32 | 33 | 34 | 35 | org.junit.jupiter 36 | junit-jupiter-api 37 | RELEASE 38 | test 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/main/java/com/github/xjjdog/passthrough/PassThroughFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough; 2 | 3 | public class PassThroughFactory { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/github/xjjdog/passthrough/hystrix/CommonHystrixConcurrencyStrategy.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.hystrix; 2 | 3 | import com.github.xjjdog.passthrough.mdc.MDCCallable; 4 | import com.github.xjjdog.passthrough.threadlocal.ThreadLocalCallable; 5 | import com.netflix.hystrix.HystrixThreadPoolKey; 6 | import com.netflix.hystrix.HystrixThreadPoolProperties; 7 | import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; 8 | import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable; 9 | import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle; 10 | import com.netflix.hystrix.strategy.properties.HystrixProperty; 11 | 12 | import java.util.concurrent.BlockingQueue; 13 | import java.util.concurrent.Callable; 14 | import java.util.concurrent.ThreadPoolExecutor; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | public class CommonHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { 18 | 19 | private HystrixConcurrencyStrategy delegate; 20 | 21 | public CommonHystrixConcurrencyStrategy(HystrixConcurrencyStrategy delegate) { 22 | this.delegate = delegate; 23 | } 24 | 25 | @Override 26 | public Callable wrapCallable(Callable callable) { 27 | Callable wrapped = this.delegate != null 28 | ? this.delegate.wrapCallable(callable) : callable; 29 | if (wrapped instanceof ThreadLocalCallable) { 30 | return wrapped; 31 | } 32 | if (wrapped instanceof MDCCallable) { 33 | return new ThreadLocalCallable<>(wrapped); 34 | } 35 | return new ThreadLocalCallable<>(new MDCCallable<>(wrapped)); 36 | } 37 | 38 | 39 | @Override 40 | public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, 41 | HystrixProperty corePoolSize, 42 | HystrixProperty maximumPoolSize, 43 | HystrixProperty keepAliveTime, TimeUnit unit, 44 | BlockingQueue workQueue) { 45 | return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, 46 | keepAliveTime, unit, workQueue); 47 | } 48 | 49 | @Override 50 | public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, 51 | HystrixThreadPoolProperties threadPoolProperties) { 52 | return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties); 53 | } 54 | 55 | @Override 56 | public BlockingQueue getBlockingQueue(int maxQueueSize) { 57 | return this.delegate.getBlockingQueue(maxQueueSize); 58 | } 59 | 60 | @Override 61 | public HystrixRequestVariable getRequestVariable( 62 | HystrixRequestVariableLifecycle rv) { 63 | return this.delegate.getRequestVariable(rv); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/github/xjjdog/passthrough/hystrix/HystrixFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.hystrix; 2 | 3 | import com.netflix.hystrix.strategy.HystrixPlugins; 4 | import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; 5 | import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; 6 | import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; 7 | import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; 8 | import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; 9 | 10 | public class HystrixFactory { 11 | public void init() { 12 | HystrixConcurrencyStrategy delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); 13 | if (delegate instanceof CommonHystrixConcurrencyStrategy) { 14 | return; 15 | } 16 | HystrixConcurrencyStrategy strategy = new CommonHystrixConcurrencyStrategy(delegate); 17 | 18 | HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins 19 | .getInstance().getCommandExecutionHook(); 20 | HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance() 21 | .getEventNotifier(); 22 | HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance() 23 | .getMetricsPublisher(); 24 | HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance() 25 | .getPropertiesStrategy(); 26 | 27 | HystrixPlugins.reset();//set all null 28 | 29 | HystrixPlugins.getInstance().registerConcurrencyStrategy(strategy); 30 | HystrixPlugins.getInstance() 31 | .registerCommandExecutionHook(commandExecutionHook); 32 | HystrixPlugins.getInstance().registerEventNotifier(eventNotifier); 33 | HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher); 34 | HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/github/xjjdog/passthrough/mdc/MDCCallable.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.mdc; 2 | 3 | 4 | import org.slf4j.MDC; 5 | 6 | import java.util.Map; 7 | import java.util.concurrent.Callable; 8 | 9 | public class MDCCallable implements Callable { 10 | 11 | private final Callable callable; 12 | 13 | private transient final Map _cm = MDC.getCopyOfContextMap(); 14 | 15 | public MDCCallable(Callable callable) { 16 | this.callable = callable; 17 | } 18 | 19 | @Override 20 | public V call() throws Exception { 21 | if (_cm != null) { 22 | MDC.setContextMap(_cm); 23 | } 24 | try { 25 | return callable.call(); 26 | } finally { 27 | MDC.clear(); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/xjjdog/passthrough/mdc/MDCRunnable.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.mdc; 2 | 3 | import org.slf4j.MDC; 4 | 5 | import java.util.Map; 6 | 7 | public class MDCRunnable implements Runnable { 8 | 9 | private final Runnable runnable; 10 | 11 | private transient final Map _cm = MDC.getCopyOfContextMap(); 12 | 13 | public MDCRunnable(Runnable runnable) { 14 | this.runnable = runnable; 15 | } 16 | 17 | @Override 18 | public void run() { 19 | if (_cm != null) { 20 | MDC.setContextMap(_cm); 21 | } 22 | try { 23 | runnable.run(); 24 | } finally { 25 | MDC.clear(); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/xjjdog/passthrough/threadlocal/ThreadLocal.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.threadlocal; 2 | 3 | @Transmissible 4 | public class ThreadLocal extends java.lang.ThreadLocal { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/github/xjjdog/passthrough/threadlocal/ThreadLocalCallable.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.threadlocal; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | public class ThreadLocalCallable implements Callable { 6 | 7 | private final Callable callable; 8 | 9 | private transient ThreadLocalHandler.Context _cm = ThreadLocalHandler.handle(); 10 | 11 | public ThreadLocalCallable(Callable callable) { 12 | this.callable = callable; 13 | } 14 | 15 | @Override 16 | public V call() throws Exception { 17 | if (_cm != null) { 18 | _cm.set(); 19 | } 20 | try { 21 | return this.callable.call(); 22 | } finally { 23 | if (_cm != null) { 24 | _cm.remove(); 25 | } 26 | } 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/xjjdog/passthrough/threadlocal/ThreadLocalHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.threadlocal; 2 | 3 | import java.lang.ThreadLocal; 4 | import java.lang.reflect.Array; 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | import java.util.Map; 9 | import java.util.WeakHashMap; 10 | 11 | public class ThreadLocalHandler { 12 | 13 | private static Field THREAD_LOCALS; 14 | 15 | private static Field THREAD_LOCAL_MAP_TABLE; 16 | 17 | private static Method THREAD_LOCAL_MAP_ENTRY_GET_METHOD; 18 | 19 | private static Field THREAD_LOCAL_MAP_ENTRY_VALUE; 20 | 21 | private static volatile boolean PREPARED = false; 22 | 23 | static { 24 | try { 25 | //ThreadLocal is java native-lib,loaded by bootstrap. 26 | //they(fields) can be cached safely. 27 | THREAD_LOCALS = Thread.class.getDeclaredField("threadLocals"); 28 | THREAD_LOCALS.setAccessible(true); 29 | 30 | Class mapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); 31 | THREAD_LOCAL_MAP_TABLE = mapClass.getDeclaredField("table"); 32 | THREAD_LOCAL_MAP_TABLE.setAccessible(true); 33 | Class entryClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry"); 34 | THREAD_LOCAL_MAP_ENTRY_VALUE = entryClass.getDeclaredField("value"); 35 | THREAD_LOCAL_MAP_ENTRY_VALUE.setAccessible(true); 36 | 37 | THREAD_LOCAL_MAP_ENTRY_GET_METHOD = entryClass.getMethod("get"); 38 | THREAD_LOCAL_MAP_ENTRY_GET_METHOD.setAccessible(true); 39 | 40 | PREPARED = true; 41 | } catch (NoSuchFieldException | NoSuchMethodException | SecurityException | ClassNotFoundException e) { 42 | // 43 | } 44 | } 45 | 46 | 47 | /** 48 | * @return null if no threadLocals 49 | * @throws Exception 50 | */ 51 | public static Context handle() { 52 | if (!PREPARED) { 53 | return null; 54 | } 55 | 56 | Thread thread = Thread.currentThread(); 57 | try { 58 | //also private,reflection 59 | Object table = THREAD_LOCAL_MAP_TABLE.get(THREAD_LOCALS.get(thread)); 60 | if (table == null) { 61 | return null; 62 | } 63 | 64 | int count = Array.getLength(table); 65 | if (count == 0) { 66 | return null; 67 | } 68 | 69 | // 70 | Map variables = new WeakHashMap<>(); 71 | for (int i = 0; i < count; i++) { 72 | Object entry = Array.get(table, i); 73 | if (entry != null) { 74 | ThreadLocal key = (ThreadLocal) THREAD_LOCAL_MAP_ENTRY_GET_METHOD.invoke(entry); 75 | if (transmissible(key)) { 76 | Object value = THREAD_LOCAL_MAP_ENTRY_VALUE.get(entry); 77 | variables.put(key, value); 78 | } 79 | } 80 | } 81 | return new Context(variables); 82 | } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { 83 | // 84 | } 85 | return null; 86 | } 87 | 88 | 89 | private static boolean transmissible(ThreadLocal target) { 90 | return target == null ? false : target.getClass().isAnnotationPresent(Transmissible.class); 91 | } 92 | 93 | 94 | public static class Context { 95 | final Map variables; 96 | final Thread parent; 97 | 98 | protected Context(Map variables) { 99 | this.variables = variables; 100 | parent = Thread.currentThread(); 101 | } 102 | 103 | public void set() { 104 | if (variables == null || variables.isEmpty() || parent == Thread.currentThread()) { 105 | return; 106 | } 107 | variables.forEach((threadLocal, value) -> { 108 | if (threadLocal != null) { 109 | threadLocal.set(value); 110 | } 111 | }); 112 | } 113 | 114 | public void remove() { 115 | if (variables == null || variables.isEmpty()) { 116 | return; 117 | } 118 | variables.forEach((threadLocal, value) -> { 119 | if (threadLocal != null) { 120 | threadLocal.remove(); 121 | } 122 | }); 123 | variables.clear(); 124 | } 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/github/xjjdog/passthrough/threadlocal/ThreadLocalRunnable.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.threadlocal; 2 | 3 | public class ThreadLocalRunnable implements Runnable { 4 | 5 | private final Runnable runnable; 6 | 7 | private transient final ThreadLocalHandler.Context _cm = ThreadLocalHandler.handle(); 8 | 9 | public ThreadLocalRunnable(Runnable runnable) { 10 | this.runnable = runnable; 11 | } 12 | 13 | @Override 14 | public void run() { 15 | if (_cm != null) { 16 | _cm.set(); 17 | } 18 | try { 19 | runnable.run(); 20 | } finally { 21 | if (_cm != null) { 22 | _cm.remove(); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/xjjdog/passthrough/threadlocal/Transmissible.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.threadlocal; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Target({ElementType.TYPE}) 6 | @Retention(RetentionPolicy.RUNTIME) 7 | @Documented 8 | public @interface Transmissible { 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/com/github/xjjdog/passthrough/threadlocal/SimpleThreadLocalNotOkTest.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.threadlocal; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | public class SimpleThreadLocalNotOkTest { 6 | @Test 7 | void testThreadLocal(){ 8 | ThreadLocal threadLocal = new ThreadLocal<>(); 9 | threadLocal.set("not ok"); 10 | new Thread(()->{ 11 | System.out.println(threadLocal.get()); 12 | }).start(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/com/github/xjjdog/passthrough/threadlocal/SimpleThreadLocalOkTest.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.threadlocal; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | public class SimpleThreadLocalOkTest { 6 | @Test 7 | void testThreadLocal() { 8 | InheritableThreadLocal threadLocal = new InheritableThreadLocal<>(); 9 | threadLocal.set("ok"); 10 | new Thread(() -> { 11 | System.out.println(threadLocal.get()); 12 | }).start(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/com/github/xjjdog/passthrough/threadlocal/ThreadLocalTest.java: -------------------------------------------------------------------------------- 1 | package com.github.xjjdog.passthrough.threadlocal; 2 | 3 | 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | 9 | public class ThreadLocalTest { 10 | 11 | @Test 12 | public void testThreadLocal() { 13 | PtClass ptClass = new PtClass(); 14 | ExecutorService executorService = Executors.newCachedThreadPool(); 15 | executorService.submit(new ThreadLocalRunnable(() -> { 16 | ptClass.print(); 17 | })); 18 | } 19 | 20 | final static class PtClass { 21 | // java.lang.ThreadLocal holder = new java.lang.ThreadLocal<>(); 22 | ThreadLocal holder = new ThreadLocal<>(); 23 | 24 | public PtClass() { 25 | holder.set("Fuck you"); 26 | } 27 | 28 | public void print() { 29 | System.out.println(holder.get()); 30 | } 31 | } 32 | } 33 | --------------------------------------------------------------------------------