背景
之前我有写过关于ThreadLocal的源码解析相关文章:一文带你读懂JDK源码:ThreadLocal类,但其实它不是业务最优解决方案,比如业界有更加先进的TransmittableThreadLocal可供选择。
下面我们按脑图来一起琢磨一下
对ThreadLocal的思考
线程封闭:不同的Thread会指向不同的ThreadLocalMap对象,从而实现了线程封闭;而这个Map的key是ThreadLocal对象,value是需要缓存的值。
思考一:每个线程都会创建一个ThreadLocalMap,里面是一个Entry数组,会不会浪费内存空间吗?
答案是不会。
1.1 内存结构
虽然每个Thread对象都有一个ThreadLocalMap,但这种设计并不是为了每个线程都创建一个全新的Map实例,而是为了实现线程局部变量的存储和访问。
每个ThreadLocal对象可以被多个线程共享,但在每个线程Thread内部,它们有自己的独立副本,即ThreadLocalMap。
这种设计允许线程安全地访问和修改数据,而不需要使用同步机制,从而提高了并发性能。
1.2 数据拓展性
同时ThreadLocalMap使用数组结构,一个Thread保存了一个ThreadLocalMap,它本质是一个Entry数组,恰好说明了,一个Thread是可以保存多个ThreadLocal对象值的。
key则是ThreadLocal.threadLocalHashCode 和 数组长度 进行位与运算的结果,恰好说明了,一个Thread是可以保存多个ThreadLocal对象值的。
这很好理解,比如在web后台系统里面,既有保存当前登录用户信息的ThreadLocal,也有保存当前请求trace_id链路标识的ThreadLocal,可能还有保存了租户id的ThreadLocal,它们都是不同的业务数据。
最终就是,一个Thread的一个Map可以存多个ThreadLocal数据,保存多个类型的数据。
这很好理解,比如在web后台系统里面,既有保存当前登录用户信息的ThreadLocal,也有保存当前请求trace_id链路标识的ThreadLocal,可能还有保存了租户id的ThreadLocal,它们都是不同的业务数据。
最终就是,一个Thread的一个Map可以存多个ThreadLocal数据。
思考二:ThreadLocal无法在父线程和子线程之间透传数据,有更好办法吗?
答案是有的。
修改方法也很简单:通过创建 InheritableThreadLocal 替代 ThreadLocal。
将
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
改为:
private static InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();
2.1 测试用例
@Slf4j
public class TestInheritableThreadLocal {
private static InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();
public static void main(String[] args) {
inheritableThreadLocal.set("main");
log.info("main - threadLocal:{}", inheritableThreadLocal.get());
new Thread(() -> {
log.info("main - thread - threadLocal:{}", inheritableThreadLocal.get());
}).start();
ThreadPoolExecutor threadPoolExecutor = ThreadPoolUtils.newThreadPool(
1, 1,
0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10), null, null);
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
// 线程池里面的线程
log.info("threadPoolExecutor - task1 - threadLocal:{}", inheritableThreadLocal.get());
// 线程池里面的线程再new线程
new Thread(() -> {
log.info("threadPoolExecutor - task1 - new thread - threadLocal:{}", inheritableThreadLocal.get());
}).start();
}
});
inheritableThreadLocal.set("main2");
log.info("main - threadLocal:{}", inheritableThreadLocal.get());
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
// 线程池里面的线程
log.info("threadPoolExecutor - task2 - threadLocal:{}", inheritableThreadLocal.get());
// 线程池里面的线程再new线程
new Thread(() -> {
log.info("threadPoolExecutor - task2 - new thread - threadLocal:{}", inheritableThreadLocal.get());
}).start();
}
});
}
}
日志
/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=58469:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/bryantmo/Desktop/code/springcloud_test/webdoor/target/test-classes:/Users/bryantmo/Desktop/code/springcloud_test/webdoor/target/classes:/Users/bryantmo/.m2/repository/com/alibaba/transmittable-thread-local/2.12.1/transmittable-thread-local-2.12.1.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-starter-netflix-eureka-client/2.2.6.RELEASE/spring-cloud-starter-netflix-eureka-client-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-starter/2.2.6.RELEASE/spring-cloud-starter-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/security/spring-security-rsa/1.0.9.RELEASE/spring-security-rsa-1.0.9.RELEASE.jar:/Users/bryantmo/.m2/repository/org/bouncycastle/bcpkix-jdk15on/1.64/bcpkix-jdk15on-1.64.jar:/Users/bryantmo/.m2/repository/org/bouncycastle/bcprov-jdk15on/1.64/bcprov-jdk15on-1.64.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-netflix-hystrix/2.2.6.RELEASE/spring-cloud-netflix-hystrix-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-netflix-eureka-client/2.2.6.RELEASE/spring-cloud-netflix-eureka-client-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/com/netflix/eureka/eureka-client/1.10.7/eureka-client-1.10.7.jar:/Users/bryantmo/.m2/repository/com/netflix/netflix-commons/netflix-eventbus/0.3.0/netflix-eventbus-0.3.0.jar:/Users/bryantmo/.m2/repository/com/netflix/netflix-commons/netflix-infix/0.3.0/netflix-infix-0.3.0.jar:/Users/bryantmo/.m2/repository/commons-jxpath/commons-jxpath/1.3/commons-jxpath-1.3.jar:/Users/bryantmo/.m2/repository/joda-time/joda-time/2.10.5/joda-time-2.10.5.jar:/Users/bryantmo/.m2/repository/org/antlr/antlr-runtime/3.4/antlr-runtime-3.4.jar:/Users/bryantmo/.m2/repository/org/antlr/stringtemplate/3.2.1/stringtemplate-3.2.1.jar:/Users/bryantmo/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/Users/bryantmo/.m2/repository/com/google/code/gson/gson/2.9.1/gson-2.9.1.jar:/Users/bryantmo/.m2/repository/org/apache/commons/commons-math/2.2/commons-math-2.2.jar:/Users/bryantmo/.m2/repository/com/netflix/archaius/archaius-core/0.7.6/archaius-core-0.7.6.jar:/Users/bryantmo/.m2/repository/javax/ws/rs/jsr311-api/1.1.1/jsr311-api-1.1.1.jar:/Users/bryantmo/.m2/repository/com/netflix/servo/servo-core/0.12.21/servo-core-0.12.21.jar:/Users/bryantmo/.m2/repository/com/sun/jersey/jersey-core/1.19.1/jersey-core-1.19.1.jar:/Users/bryantmo/.m2/repository/com/sun/jersey/jersey-client/1.19.1/jersey-client-1.19.1.jar:/Users/bryantmo/.m2/repository/com/sun/jersey/contribs/jersey-apache-client4/1.19.1/jersey-apache-client4-1.19.1.jar:/Users/bryantmo/.m2/repository/commons-configuration/commons-configuration/1.10/commons-configuration-1.10.jar:/Users/bryantmo/.m2/repository/com/google/inject/guice/4.1.0/guice-4.1.0.jar:/Users/bryantmo/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/Users/bryantmo/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.10.3/jackson-core-2.10.3.jar:/Users/bryantmo/.m2/repository/org/codehaus/jettison/jettison/1.3.7/jettison-1.3.7.jar:/Users/bryantmo/.m2/repository/stax/stax-api/1.0.1/stax-api-1.0.1.jar:/Users/bryantmo/.m2/repository/com/netflix/eureka/eureka-core/1.10.7/eureka-core-1.10.7.jar:/Users/bryantmo/.m2/repository/com/fasterxml/woodstox/woodstox-core/5.3.0/woodstox-core-5.3.0.jar:/Users/bryantmo/.m2/repository/org/codehaus/woodstox/stax2-api/4.2/stax2-api-4.2.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-starter-netflix-archaius/2.2.6.RELEASE/spring-cloud-starter-netflix-archaius-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-netflix-archaius/2.2.6.RELEASE/spring-cloud-netflix-archaius-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-starter-loadbalancer/2.2.6.RELEASE/spring-cloud-starter-loadbalancer-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-loadbalancer/2.2.6.RELEASE/spring-cloud-loadbalancer-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/io/projectreactor/addons/reactor-extra/3.3.3.RELEASE/reactor-extra-3.3.3.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-starter-cache/2.2.6.RELEASE/spring-boot-starter-cache-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/com/stoyanr/evictor/1.0.0/evictor-1.0.0.jar:/Users/bryantmo/.m2/repository/com/netflix/ribbon/ribbon-eureka/2.3.0/ribbon-eureka-2.3.0.jar:/Users/bryantmo/.m2/repository/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar:/Users/bryantmo/.m2/repository/com/thoughtworks/xstream/xstream/1.4.13/xstream-1.4.13.jar:/Users/bryantmo/.m2/repository/xmlpull/xmlpull/1.1.3.1/xmlpull-1.1.3.1.jar:/Users/bryantmo/.m2/repository/xpp3/xpp3_min/1.1.4c/xpp3_min-1.1.4c.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-config-client/2.2.6.RELEASE/spring-cloud-config-client-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.2.6.RELEASE/spring-boot-autoconfigure-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot/2.2.6.RELEASE/spring-boot-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-commons/2.2.6.RELEASE/spring-cloud-commons-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/security/spring-security-crypto/5.2.2.RELEASE/spring-security-crypto-5.2.2.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-context/2.2.6.RELEASE/spring-cloud-context-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-web/5.2.5.RELEASE/spring-web-5.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-beans/5.2.5.RELEASE/spring-beans-5.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.10.3/jackson-annotations-2.10.3.jar:/Users/bryantmo/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.10.3/jackson-databind-2.10.3.jar:/Users/bryantmo/.m2/repository/org/apache/httpcomponents/httpclient/4.5.12/httpclient-4.5.12.jar:/Users/bryantmo/.m2/repository/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar:/Users/bryantmo/.m2/repository/commons-codec/commons-codec/1.13/commons-codec-1.13.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-starter-netflix-ribbon/2.2.6.RELEASE/spring-cloud-starter-netflix-ribbon-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-netflix-ribbon/2.2.6.RELEASE/spring-cloud-netflix-ribbon-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/com/netflix/ribbon/ribbon/2.3.0/ribbon-2.3.0.jar:/Users/bryantmo/.m2/repository/com/netflix/ribbon/ribbon-transport/2.3.0/ribbon-transport-2.3.0.jar:/Users/bryantmo/.m2/repository/io/reactivex/rxnetty-contexts/0.4.9/rxnetty-contexts-0.4.9.jar:/Users/bryantmo/.m2/repository/io/reactivex/rxnetty-servo/0.4.9/rxnetty-servo-0.4.9.jar:/Users/bryantmo/.m2/repository/javax/inject/javax.inject/1/javax.inject-1.jar:/Users/bryantmo/.m2/repository/io/reactivex/rxnetty/0.4.9/rxnetty-0.4.9.jar:/Users/bryantmo/.m2/repository/com/netflix/ribbon/ribbon-core/2.3.0/ribbon-core-2.3.0.jar:/Users/bryantmo/.m2/repository/commons-lang/commons-lang/2.6/commons-lang-2.6.jar:/Users/bryantmo/.m2/repository/com/netflix/ribbon/ribbon-httpclient/2.3.0/ribbon-httpclient-2.3.0.jar:/Users/bryantmo/.m2/repository/commons-collections/commons-collections/3.2.2/commons-collections-3.2.2.jar:/Users/bryantmo/.m2/repository/com/netflix/netflix-commons/netflix-commons-util/0.3.0/netflix-commons-util-0.3.0.jar:/Users/bryantmo/.m2/repository/com/netflix/ribbon/ribbon-loadbalancer/2.3.0/ribbon-loadbalancer-2.3.0.jar:/Users/bryantmo/.m2/repository/com/netflix/netflix-commons/netflix-statistics/0.1.1/netflix-statistics-0.1.1.jar:/Users/bryantmo/.m2/repository/io/reactivex/rxjava/1.3.8/rxjava-1.3.8.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-starter-netflix-hystrix/2.2.6.RELEASE/spring-cloud-starter-netflix-hystrix-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/com/netflix/hystrix/hystrix-core/1.5.18/hystrix-core-1.5.18.jar:/Users/bryantmo/.m2/repository/org/hdrhistogram/HdrHistogram/2.1.9/HdrHistogram-2.1.9.jar:/Users/bryantmo/.m2/repository/com/netflix/hystrix/hystrix-serialization/1.5.18/hystrix-serialization-1.5.18.jar:/Users/bryantmo/.m2/repository/com/fasterxml/jackson/module/jackson-module-afterburner/2.10.3/jackson-module-afterburner-2.10.3.jar:/Users/bryantmo/.m2/repository/com/netflix/hystrix/hystrix-metrics-event-stream/1.5.18/hystrix-metrics-event-stream-1.5.18.jar:/Users/bryantmo/.m2/repository/com/netflix/hystrix/hystrix-javanica/1.5.18/hystrix-javanica-1.5.18.jar:/Users/bryantmo/.m2/repository/org/ow2/asm/asm/5.0.4/asm-5.0.4.jar:/Users/bryantmo/.m2/repository/org/aspectj/aspectjweaver/1.9.5/aspectjweaver-1.9.5.jar:/Users/bryantmo/.m2/repository/io/reactivex/rxjava-reactive-streams/1.2.1/rxjava-reactive-streams-1.2.1.jar:/Users/bryantmo/.m2/repository/org/reactivestreams/reactive-streams/1.0.3/reactive-streams-1.0.3.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-starter-actuator/3.3.2/spring-boot-starter-actuator-3.3.2.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-starter/2.2.6.RELEASE/spring-boot-starter-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.2.6.RELEASE/spring-boot-starter-logging-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/bryantmo/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/bryantmo/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.12.1/log4j-to-slf4j-2.12.1.jar:/Users/bryantmo/.m2/repository/org/apache/logging/log4j/log4j-api/2.12.1/log4j-api-2.12.1.jar:/Users/bryantmo/.m2/repository/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar:/Users/bryantmo/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/bryantmo/.m2/repository/org/yaml/snakeyaml/1.25/snakeyaml-1.25.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-actuator-autoconfigure/2.2.6.RELEASE/spring-boot-actuator-autoconfigure-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-actuator/2.2.6.RELEASE/spring-boot-actuator-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-context/5.2.5.RELEASE/spring-context-5.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.10.3/jackson-datatype-jsr310-2.10.3.jar:/Users/bryantmo/.m2/repository/io/micrometer/micrometer-observation/1.13.2/micrometer-observation-1.13.2.jar:/Users/bryantmo/.m2/repository/io/micrometer/micrometer-commons/1.13.2/micrometer-commons-1.13.2.jar:/Users/bryantmo/.m2/repository/io/micrometer/micrometer-jakarta9/1.13.2/micrometer-jakarta9-1.13.2.jar:/Users/bryantmo/.m2/repository/io/micrometer/micrometer-core/1.3.6/micrometer-core-1.3.6.jar:/Users/bryantmo/.m2/repository/org/latencyutils/LatencyUtils/2.0.3/LatencyUtils-2.0.3.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-starter-openfeign/2.2.6.RELEASE/spring-cloud-starter-openfeign-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-openfeign-core/2.2.6.RELEASE/spring-cloud-openfeign-core-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/io/github/openfeign/form/feign-form-spring/3.8.0/feign-form-spring-3.8.0.jar:/Users/bryantmo/.m2/repository/io/github/openfeign/form/feign-form/3.8.0/feign-form-3.8.0.jar:/Users/bryantmo/.m2/repository/commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4.jar:/Users/bryantmo/.m2/repository/commons-io/commons-io/2.2/commons-io-2.2.jar:/Users/bryantmo/.m2/repository/io/github/openfeign/feign-core/10.10.1/feign-core-10.10.1.jar:/Users/bryantmo/.m2/repository/io/github/openfeign/feign-slf4j/10.10.1/feign-slf4j-10.10.1.jar:/Users/bryantmo/.m2/repository/io/github/openfeign/feign-hystrix/10.10.1/feign-hystrix-10.10.1.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.2.6.RELEASE/spring-boot-starter-web-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.2.6.RELEASE/spring-boot-starter-json-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.10.3/jackson-datatype-jdk8-2.10.3.jar:/Users/bryantmo/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.10.3/jackson-module-parameter-names-2.10.3.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.2.6.RELEASE/spring-boot-starter-tomcat-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.33/tomcat-embed-core-9.0.33.jar:/Users/bryantmo/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.33/tomcat-embed-el-9.0.33.jar:/Users/bryantmo/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.33/tomcat-embed-websocket-9.0.33.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-starter-validation/2.2.6.RELEASE/spring-boot-starter-validation-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/jakarta/validation/jakarta.validation-api/2.0.2/jakarta.validation-api-2.0.2.jar:/Users/bryantmo/.m2/repository/org/hibernate/validator/hibernate-validator/6.0.18.Final/hibernate-validator-6.0.18.Final.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-webmvc/5.2.5.RELEASE/spring-webmvc-5.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-aop/5.2.5.RELEASE/spring-aop-5.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-expression/5.2.5.RELEASE/spring-expression-5.2.5.RELEASE.jar:/Users/bryantmo/Desktop/code/springcloud_test/user-feign-service-api/target/classes:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-starter-data-redis-reactive/3.3.2/spring-boot-starter-data-redis-reactive-3.3.2.jar:/Users/bryantmo/.m2/repository/io/lettuce/lettuce-core/5.2.2.RELEASE/lettuce-core-5.2.2.RELEASE.jar:/Users/bryantmo/.m2/repository/io/netty/netty-common/4.1.48.Final/netty-common-4.1.48.Final.jar:/Users/bryantmo/.m2/repository/io/netty/netty-handler/4.1.48.Final/netty-handler-4.1.48.Final.jar:/Users/bryantmo/.m2/repository/io/netty/netty-resolver/4.1.48.Final/netty-resolver-4.1.48.Final.jar:/Users/bryantmo/.m2/repository/io/netty/netty-buffer/4.1.48.Final/netty-buffer-4.1.48.Final.jar:/Users/bryantmo/.m2/repository/io/netty/netty-codec/4.1.48.Final/netty-codec-4.1.48.Final.jar:/Users/bryantmo/.m2/repository/io/netty/netty-transport/4.1.48.Final/netty-transport-4.1.48.Final.jar:/Users/bryantmo/.m2/repository/io/projectreactor/reactor-core/3.3.4.RELEASE/reactor-core-3.3.4.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/data/spring-data-redis/2.2.6.RELEASE/spring-data-redis-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/data/spring-data-keyvalue/2.2.6.RELEASE/spring-data-keyvalue-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/data/spring-data-commons/2.2.6.RELEASE/spring-data-commons-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-tx/5.2.5.RELEASE/spring-tx-5.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-oxm/5.2.5.RELEASE/spring-oxm-5.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-context-support/5.2.5.RELEASE/spring-context-support-5.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-starter-sleuth/2.2.6.RELEASE/spring-cloud-starter-sleuth-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-starter-aop/2.2.6.RELEASE/spring-boot-starter-aop-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-sleuth-core/2.2.6.RELEASE/spring-cloud-sleuth-core-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/aspectj/aspectjrt/1.9.5/aspectjrt-1.9.5.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave/5.12.7/brave-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-context-slf4j/5.12.7/brave-context-slf4j-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-messaging/5.12.7/brave-instrumentation-messaging-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-rpc/5.12.7/brave-instrumentation-rpc-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-spring-web/5.12.7/brave-instrumentation-spring-web-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-http/5.12.7/brave-instrumentation-http-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-spring-rabbit/5.12.7/brave-instrumentation-spring-rabbit-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-kafka-clients/5.12.7/brave-instrumentation-kafka-clients-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-kafka-streams/5.12.7/brave-instrumentation-kafka-streams-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-httpclient/5.12.7/brave-instrumentation-httpclient-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-httpasyncclient/5.12.7/brave-instrumentation-httpasyncclient-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-spring-webmvc/5.12.7/brave-instrumentation-spring-webmvc-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-servlet/5.12.7/brave-instrumentation-servlet-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/brave/brave-instrumentation-jms/5.12.7/brave-instrumentation-jms-5.12.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/reporter2/zipkin-reporter-metrics-micrometer/2.15.2/zipkin-reporter-metrics-micrometer-2.15.2.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-starter-zipkin/2.2.6.RELEASE/spring-cloud-starter-zipkin-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/cloud/spring-cloud-sleuth-zipkin/2.2.6.RELEASE/spring-cloud-sleuth-zipkin-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/io/zipkin/zipkin2/zipkin/2.21.7/zipkin-2.21.7.jar:/Users/bryantmo/.m2/repository/io/zipkin/reporter2/zipkin-reporter/2.15.2/zipkin-reporter-2.15.2.jar:/Users/bryantmo/.m2/repository/io/zipkin/reporter2/zipkin-reporter-brave/2.15.2/zipkin-reporter-brave-2.15.2.jar:/Users/bryantmo/.m2/repository/io/zipkin/reporter2/zipkin-sender-kafka/2.15.2/zipkin-sender-kafka-2.15.2.jar:/Users/bryantmo/.m2/repository/io/zipkin/reporter2/zipkin-sender-activemq-client/2.15.2/zipkin-sender-activemq-client-2.15.2.jar:/Users/bryantmo/.m2/repository/io/zipkin/reporter2/zipkin-sender-amqp-client/2.15.2/zipkin-sender-amqp-client-2.15.2.jar:/Users/bryantmo/.m2/repository/org/apache/commons/commons-collections4/4.4/commons-collections4-4.4.jar:/Users/bryantmo/.m2/repository/com/google/guava/guava/30.1.1-jre/guava-30.1.1-jre.jar:/Users/bryantmo/.m2/repository/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar:/Users/bryantmo/.m2/repository/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:/Users/bryantmo/.m2/repository/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar:/Users/bryantmo/.m2/repository/org/checkerframework/checker-qual/3.8.0/checker-qual-3.8.0.jar:/Users/bryantmo/.m2/repository/com/google/errorprone/error_prone_annotations/2.5.1/error_prone_annotations-2.5.1.jar:/Users/bryantmo/.m2/repository/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar:/Users/bryantmo/.m2/repository/javax/servlet/javax.servlet-api/4.0.1/javax.servlet-api-4.0.1.jar:/Users/bryantmo/.m2/repository/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar:/Users/bryantmo/.m2/repository/org/projectlombok/lombok/1.18.18/lombok-1.18.18.jar:/Users/bryantmo/.m2/repository/org/springframework/retry/spring-retry/1.2.5.RELEASE/spring-retry-1.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-core/5.2.5.RELEASE/spring-core-5.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-jcl/5.2.5.RELEASE/spring-jcl-5.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-starter-test/2.2.6.RELEASE/spring-boot-starter-test-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-test/2.2.6.RELEASE/spring-boot-test-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/org/springframework/boot/spring-boot-test-autoconfigure/2.2.6.RELEASE/spring-boot-test-autoconfigure-2.2.6.RELEASE.jar:/Users/bryantmo/.m2/repository/com/jayway/jsonpath/json-path/2.4.0/json-path-2.4.0.jar:/Users/bryantmo/.m2/repository/net/minidev/json-smart/2.3/json-smart-2.3.jar:/Users/bryantmo/.m2/repository/net/minidev/accessors-smart/1.2/accessors-smart-1.2.jar:/Users/bryantmo/.m2/repository/jakarta/xml/bind/jakarta.xml.bind-api/2.3.3/jakarta.xml.bind-api-2.3.3.jar:/Users/bryantmo/.m2/repository/jakarta/activation/jakarta.activation-api/1.2.2/jakarta.activation-api-1.2.2.jar:/Users/bryantmo/.m2/repository/org/junit/vintage/junit-vintage-engine/5.5.2/junit-vintage-engine-5.5.2.jar:/Users/bryantmo/.m2/repository/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar:/Users/bryantmo/.m2/repository/org/junit/platform/junit-platform-engine/1.5.2/junit-platform-engine-1.5.2.jar:/Users/bryantmo/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/bryantmo/.m2/repository/org/mockito/mockito-junit-jupiter/3.1.0/mockito-junit-jupiter-3.1.0.jar:/Users/bryantmo/.m2/repository/org/assertj/assertj-core/3.13.2/assertj-core-3.13.2.jar:/Users/bryantmo/.m2/repository/org/hamcrest/hamcrest/2.1/hamcrest-2.1.jar:/Users/bryantmo/.m2/repository/org/mockito/mockito-core/3.1.0/mockito-core-3.1.0.jar:/Users/bryantmo/.m2/repository/net/bytebuddy/byte-buddy/1.10.8/byte-buddy-1.10.8.jar:/Users/bryantmo/.m2/repository/net/bytebuddy/byte-buddy-agent/1.10.8/byte-buddy-agent-1.10.8.jar:/Users/bryantmo/.m2/repository/org/objenesis/objenesis/2.6/objenesis-2.6.jar:/Users/bryantmo/.m2/repository/org/skyscreamer/jsonassert/1.5.0/jsonassert-1.5.0.jar:/Users/bryantmo/.m2/repository/com/vaadin/external/google/android-json/0.0.20131108.vaadin1/android-json-0.0.20131108.vaadin1.jar:/Users/bryantmo/.m2/repository/org/springframework/spring-test/5.2.5.RELEASE/spring-test-5.2.5.RELEASE.jar:/Users/bryantmo/.m2/repository/org/xmlunit/xmlunit-core/2.6.4/xmlunit-core-2.6.4.jar:/Users/bryantmo/.m2/repository/com/google/re2j/re2j/1.3/re2j-1.3.jar:/Users/bryantmo/.m2/repository/org/junit/jupiter/junit-jupiter/5.5.2/junit-jupiter-5.5.2.jar:/Users/bryantmo/.m2/repository/org/junit/jupiter/junit-jupiter-api/5.5.2/junit-jupiter-api-5.5.2.jar:/Users/bryantmo/.m2/repository/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar:/Users/bryantmo/.m2/repository/org/junit/platform/junit-platform-commons/1.5.2/junit-platform-commons-1.5.2.jar:/Users/bryantmo/.m2/repository/org/junit/jupiter/junit-jupiter-params/5.5.2/junit-jupiter-params-5.5.2.jar:/Users/bryantmo/.m2/repository/org/junit/jupiter/junit-jupiter-engine/5.5.2/junit-jupiter-engine-5.5.2.jar:/Users/bryantmo/.m2/repository/org/hibernate/hibernate-validator/5.4.3.Final/hibernate-validator-5.4.3.Final.jar:/Users/bryantmo/.m2/repository/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar:/Users/bryantmo/.m2/repository/org/jboss/logging/jboss-logging/3.4.1.Final/jboss-logging-3.4.1.Final.jar:/Users/bryantmo/.m2/repository/com/fasterxml/classmate/1.5.1/classmate-1.5.1.jar com.bryant.ttl.TestInheritableThreadLocal
21:04:39.203 [main] INFO com.bryant.ttl.TestInheritableThreadLocal - main - threadLocal:main
21:04:39.204 [Thread-0] INFO com.bryant.ttl.TestInheritableThreadLocal - main - thread - threadLocal:main
21:04:39.207 [main] INFO com.bryant.ttl.TestInheritableThreadLocal - main - threadLocal:main2
21:04:39.207 [pool-1-thread-1] INFO com.bryant.ttl.TestInheritableThreadLocal - threadPoolExecutor - task1 - threadLocal:main
21:04:39.207 [Thread-1] INFO com.bryant.ttl.TestInheritableThreadLocal - threadPoolExecutor - task1 - new thread - threadLocal:main
21:04:39.208 [pool-1-thread-1] INFO com.bryant.ttl.TestInheritableThreadLocal - threadPoolExecutor - task2 - threadLocal:main
21:04:39.208 [Thread-2] INFO com.bryant.ttl.TestInheritableThreadLocal - threadPoolExecutor - task2 - new thread - threadLocal:main
分析
- 子线程可以获取父线程的上下文内容
- 无论是通过ThreadPoolExecutor创建的线程,还是通过new Thread()创建的线程,都可以达到透传数据的目的
- 但是,线程池执行两个异步线程里,主线程已经对InheritableThreadLocal进行了新的赋值(mian2字符串),但threadPoolExecutor第二次执行任务时,没有准确读入最新的InheritableThreadLocal,而是复用了旧线程的赋值(main字符串)
2.2 源码:InheritableThreadLocal
InheritableThreadLocal#set
public void set(T value) {
Thread t = Thread.currentThread();
// 【1】
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
// 【2】
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
代码分析:
- 【1】getMap(t):如果线程T有inheritableThreadLocals,则返回它
- 【2】createMap(t, value):如果线程T没有inheritableThreadLocals,new了一个ThreadLocalMap,并赋值线程T的inheritableThreadLocals
我们观察到Thread除了包含threadLocals,还另外定义了inheritableThreadLocals。
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
InheritableThreadLocal#get
public T get() {
Thread t = Thread.currentThread();
// 【3】
ThreadLocalMap map = getMap(t);
if (map != null) {
// 【4】
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 【5】
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
代码分析:
- 【3】获取线程T的inheritableThreadLocals
- 【4】如果不为空,返回ThreadLocalMap的值
- 【5】如果为空,初始化ThreadLocalMap,并赋值线程T的inheritableThreadLocals
Thread的构造器
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//....
Thread parent = currentThread();
// 此处进行inheritThreadLocals的拷贝复制
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//....
}
代码分析:
- 在新建线程时,会拷贝父线程的inheritableThreadLocals,到子线程的inheritableThreadLocals
思考三:InheritableThreadLocal有透传数据的优点,是否有坑?
答案是有的。
比如我们的MVC框架,默认是创建了一个名为“applicationTaskExecutor”线程池,所以一定存在线程复用的场景。
那么问题就来了。
如果请求1来了,上下文(用户名=A)放入了InheritableThreadLocal,然后处理掉请求1;
但由于线程复用,请求1结束处理之后,业务侧忘记清理上下文内存了,或者由于bug出现,漏设置新的上下文了;
“applicationTaskExecutor”线程池会用这个子线程继续处理请求2,此时的真实上下文是(用户名=B),但子线程拿到的还是(用户名=A);
这也就是说,出现了内存泄漏。
解决办法也很多,在业务侧进行设计
- 设置拦截器的preHandler,在一个请求处理前,对InheritableThreadLocal的数据赋值,设置最新的值
- 设置拦截器的afterHandler,在一个请求处理完后,对InheritableThreadLocal的数据进行remove
但这个方案依旧有漏洞,因为业务内部也有线程池,这时候拦截器就无法对内部的线程进行拦截了,所以我们有更好的工具选择:阿里开源的TransmittableThreadLocal。
思考四:TransmittableThreadLocal的原理是什么?
4.1 测试用例
@Slf4j
public class TestTransmittableThreadLocal {
private static TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal();
public static void main(String[] args) {
transmittableThreadLocal.set("main");
log.info("main - threadLocal:{}", transmittableThreadLocal.get());
new Thread(() -> {
log.info("thread1 - threadLocal:{}", transmittableThreadLocal.get());
transmittableThreadLocal.set("thread-1");
log.info("thread1 - after set - threadLocal:{}", transmittableThreadLocal.get());
}).start();
Executor threadPoolExecutor = TtlExecutors.getTtlExecutor(
ThreadPoolUtils.newThreadPool(
1, 1,
0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10), null, null));
// 构造一个新的TtlRunnable
TtlRunnable ttlRunnable = TtlRunnable.get(new Runnable() {
@Override
public void run() {
// 线程池里面的线程
log.info("threadPoolExecutor - task - threadLocal:{}", transmittableThreadLocal.get());
// 线程池里面的线程再new线程
new Thread(() -> {
log.info("threadPoolExecutor - task - thread - threadLocal:{}", transmittableThreadLocal.get());
}).start();
}
});
threadPoolExecutor.execute(ttlRunnable);
transmittableThreadLocal.set("main2");
log.info("main - threadLocal:{}", transmittableThreadLocal.get());
// 构造一个新的TtlRunnable
TtlRunnable ttlRunnable2 =TtlRunnable.get(new Runnable() {
@Override
public void run() {
// 线程池里面的线程
log.info("threadPoolExecutor - task2 - threadLocal:{}", transmittableThreadLocal.get());
// 线程池里面的线程再new线程
new Thread(() -> {
log.info("threadPoolExecutor - task2 - new thread - threadLocal:{}", transmittableThreadLocal.get());
}).start();
}
});
threadPoolExecutor.execute(ttlRunnable2);
}
}
日志
22:01:38.358 [main] INFO com.bryant.ttl.TestTransmittableThreadLocal - main - threadLocal:main
22:01:38.360 [Thread-0] INFO com.bryant.ttl.TestTransmittableThreadLocal - thread1 - threadLocal:main
22:01:38.361 [Thread-0] INFO com.bryant.ttl.TestTransmittableThreadLocal - thread1 - after set - threadLocal:thread-1
22:01:38.363 [main] INFO com.bryant.ttl.TestTransmittableThreadLocal - main - threadLocal:main2
22:01:38.364 [pool-1-thread-1] INFO com.bryant.ttl.TestTransmittableThreadLocal - threadPoolExecutor - task - threadLocal:main
22:01:38.364 [pool-1-thread-1] INFO com.bryant.ttl.TestTransmittableThreadLocal - threadPoolExecutor - task2 - threadLocal:main2
22:01:38.364 [Thread-1] INFO com.bryant.ttl.TestTransmittableThreadLocal - threadPoolExecutor - task - thread - threadLocal:main
22:01:38.365 [Thread-2] INFO com.bryant.ttl.TestTransmittableThreadLocal - threadPoolExecutor - task2 - new thread - threadLocal:main2
分析
- 子线程可以获取父线程的上下文内容
- 无论是通过ThreadPoolExecutor创建的线程,还是通过new Thread()创建的线程,都可以达到透传数据的目的
- 对于main线程做的修改,即使是通过线程复用,也能够实时透传给子线程
4.2 源码:分析TransmittableThreadLocal
private static TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal();
TransmittableThreadLocal#set
private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?>
initialValue() {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
}
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?>
childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
}
};
@Override
public final void set(T value) {
if (!disableIgnoreNullValueSemantics && null == value) {
// 【1】
remove();
} else {
// 【2】
super.set(value);
// 【3】
addThisToHolder();
}
}
@Override
public final void remove() {
removeThisFromHolder();
super.remove();
}
private void removeThisFromHolder() {
holder.get().remove(this);
}
// java.lang.ThreadLocal#remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
代码分析:
- 【1】若 disableIgnoreNullValueSemantics 为 false 且传入值 value 为 null,则调用 remove() 方法移除值。
- 【2】否则,调用父类的 set 方法设置值,即,java.lang.ThreadLocal#set
- 【3】并通过 addThisToHolder 将当前对象添加TransmittableThreadLocal的holder中
TransmittableThreadLocal#get
@Override
public final T get() {
T value = super.get();
if (disableIgnoreNullValueSemantics || null != value) addThisToHolder();
return value;
}
private void addThisToHolder() {
if (!holder.get().containsKey(this)) {
holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
}
}
代码分析:
- 从父类获取缓存值 value。
- 检查是否禁用忽略空值语义或 value 非空,若是,则将当前对象添加到持有者中。
- 返回获取的 value。
TtlExecutors#getTtlExecutor
public final class TtlExecutors {
@Nullable
public static Executor getTtlExecutor(@Nullable Executor executor) {
// 【1】
if (TtlAgent.isTtlAgentLoaded() || null == executor || executor instanceof TtlEnhanced) {
return executor;
}
// 【2】
return new ExecutorTtlWrapper(executor, true);
}
}
class ExecutorTtlWrapper implements Executor, TtlWrapper<Executor>, TtlEnhanced {
private final Executor executor;
protected final boolean idempotent;
//【3】
ExecutorTtlWrapper(@NonNull Executor executor, boolean idempotent) {
this.executor = executor;
this.idempotent = idempotent;
}
}
代码分析:
- 【1】若Ttl代理已加载或传入执行器为null或已是Ttl增强版,则直接返回传入的执行器
- 【2】否则,返回一个包装后的ExecutorTtlWrapper实例。
- 【3】构造器会把真正执行任务的线程池,封装起来,而ExecutorTtlWrapper作为一个代理类被调用。
TtlRunnable#get
public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
if (null == runnable) return null;
if (runnable instanceof TtlEnhanced) {
// avoid redundant decoration, and ensure idempotency
if (idempotent) return (TtlRunnable) runnable;
else throw new IllegalStateException("Already TtlRunnable!");
}
return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
代码分析:
根据给定的参数获取一个TtlRunnable实例:
- 如果传入的runnable为null,返回null。
- 如果runnable是TtlEnhanced的实例且idempotent为true,直接返回runnable作为TtlRunnable。
- 否则,创建一个新的TtlRunnable实例并返回。如果runnable已是TtlRunnable实例且idempotent为false,抛出异常。
核心:创建一个新的TtlRunnable实例
Snapshot快照生成
private final AtomicReference<Object> capturedRef;
public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments {
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
//【1】
this.capturedRef = new AtomicReference<Object>(capture());
//【2】
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
}
public static class Transmitter {
@NonNull
public static Object capture() {
// 【1.1】
return new Snapshot(captureTtlValues(), captureThreadLocalValues());
}
private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
// 【1.2】
ttl2Value.put(threadLocal, threadLocal.copyValue());
}
return ttl2Value;
}
private static HashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
final ThreadLocal<Object> threadLocal = entry.getKey();
final TtlCopier<Object> copier = entry.getValue();
// 【1.3】
threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
}
return threadLocal2Value;
}
}
private static class Snapshot {
final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value;
final HashMap<ThreadLocal<Object>, Object> threadLocal2Value;
private Snapshot(HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value, HashMap<ThreadLocal<Object>, Object> threadLocal2Value) {
this.ttl2Value = ttl2Value;
this.threadLocal2Value = threadLocal2Value;
}
}
代码分析:
- 【1】capturedRef方法取到快照对象Snapshot,赋值给AtomicReference原子类- captureTtlValues():Snapshot的ttl2Value,数据来源于holder的注册值- captureThreadLocalValues():Snapshot的threadLocal2Value,数据来源于threadLocalHolder的注册值- capture():构造了Snapshot对象
- 【2】任务缓存起来
通过上面的源码分析,我们可以得到下面的流程图:
只要异步线程执行前,调用了TransmittableThreadLocal#set,那么就会触发addThisToHolder对holder的TransmittableThreadLocal进行数据更新
Snapshot快照重放
public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments {
@Override
public void run() {
// 【1】
final Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
//【2】
final Object backup = replay(captured);
try {
// 【3】
runnable.run();
} finally {
// 【4】
restore(backup);
}
}
@NonNull
public static Object replay(@NonNull Object captured) {
final Snapshot capturedSnapshot = (Snapshot) captured;
return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
}
public static void restore(@NonNull Object backup) {
final Snapshot backupSnapshot = (Snapshot) backup;
restoreTtlValues(backupSnapshot.ttl2Value);
restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
}
}
代码分析:
- 线程池执行TtlRunnable任务时:- holder值恢复- threadLocalHolder值恢复- 【1】任务执行前 - 获取快照Snapshot1- 【2】任务执行前 - 重新构造一个新快照对象Snapshot2- 【3】任务执行 runnable.run();- 【4】任务执行后 - 重放Snapshot2
通过上面的源码分析,我们可以得到下面的流程图:
确保了线程复用时,能够获取到线程执行前最新的数据,而不用担心上一次执行后留下的脏数据结果
ExecutorTtlWrapper#execute:封装好的TTL线程池执行任务
class ExecutorTtlWrapper implements Executor, TtlWrapper<Executor>, TtlEnhanced {
private final Executor executor;
@Override
public void execute(@NonNull Runnable command) {
executor.execute(TtlRunnable.get(command, false, idempotent));
}
}
代码分析:
- TtlRunnable.get:参考上面的 TtlRunnable#get
- executor.execute:封装好的线程池,执行任务即可
思考五:三种工具对比
优点
缺点
ThreadLocal
简单使用,每个线程都有独立的变量副本,避免了线程安全问题。
不能跨线程传递变量值,不适用于需要在线程间共享数据的场景。
InheritableThreadLocal
允许父子线程间传递变量值,适用于需要传递上下文信息的场景。
不适用于线程池的线程复用场景,因为线程池中的线程可能不是父子关系。
另外,InheritableThreadLocal的传递是发生在新建线程的时候,但是在我们项目中基本都是用线程池来使用多线程,因为线程的创建销毁都会有资源消耗,当然也有可能发生oom
TransmittableThreadLocal
支持在线程池等场景中传递变量值,适用于需要跨多个线程共享数据的场景
相比ThreadLocal和InheritableThreadLocal,实现更复杂,使用时需要额外注意线程安全
拓展:SpringMVC异步线程池去接每个请求的,可能出现的内存泄漏?
因为SpringBoot应用,会通过@SpringBootApplication来自动加载相关系统配置类,所以我们了解一下加载类,先看spring.factories:
里面有一个类:
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
TaskExecutionAutoConfiguration
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {
/**
* Bean name of the application {@link TaskExecutor}.
*/
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
@Bean
@ConditionalOnMissingBean
public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties,
ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
ObjectProvider<TaskDecorator> taskDecorator) {
TaskExecutionProperties.Pool pool = properties.getPool();
TaskExecutorBuilder builder = new TaskExecutorBuilder();
builder = builder.queueCapacity(pool.getQueueCapacity());
builder = builder.corePoolSize(pool.getCoreSize());
builder = builder.maxPoolSize(pool.getMaxSize());
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
builder = builder.keepAlive(pool.getKeepAlive());
Shutdown shutdown = properties.getShutdown();
builder = builder.awaitTermination(shutdown.isAwaitTermination());
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);
builder = builder.taskDecorator(taskDecorator.getIfUnique());
return builder;
}
@Lazy
@Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,
AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
@ConditionalOnMissingBean(Executor.class)
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.build();
}
}
代码分析:
- 这个配置类,强制要求有ThreadPoolTaskExecutor才可以初始化
- TaskExecutionProperties:spring.task.execution开头的配置项,我们可以基于此进行定制化
- 通过建造者模式,使用TaskExecutorBuilder进行的ThreadPoolTaskExecutor封装构建。
其他文章
深刻理解Redis集群(上):RDB快照和AOF日志
深刻理解Redis集群(中):Redis主从数据同步模式
深刻理解Redis集群(下):Redis 哨兵(Sentinel)模式
Kafka消息堆积问题排查
基于SpringMVC的API灰度方案
理解到位:灾备和只读数据库
SQL治理经验谈:索引覆盖
Mybatis链路分析:JDK动态代理和责任链模式的应用
大模型安装部署、测试、接入SpringCloud应用体系
Mybatis插件-租户ID的注入&拦截应用
版权归原作者 后台技术汇 所有, 如有侵权,请联系我们删除。