0


线程安全反思录(上):ThreadLocal到底安全不?

3ebefb3c569ce5d72f7526d7777e32ef.jpeg

背景

之前我有写过关于ThreadLocal的源码解析相关文章:一文带你读懂JDK源码:ThreadLocal类,但其实它不是业务最优解决方案,比如业界有更加先进的TransmittableThreadLocal可供选择。

下面我们按脑图来一起琢磨一下

1b322aa204e3d71234022d806f3d23c1.png

对ThreadLocal的思考

线程封闭:不同的Thread会指向不同的ThreadLocalMap对象,从而实现了线程封闭;而这个Map的key是ThreadLocal对象,value是需要缓存的值。

思考一:每个线程都会创建一个ThreadLocalMap,里面是一个Entry数组,会不会浪费内存空间吗?

答案是不会。

1.1 内存结构

4d12379774aab5f7fcdcda7166212891.png

虽然每个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的原理是什么?

3e33a5ea3858df7e83a1cadd89c59e33.png

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进行数据更新

24e0ec050450ae701ee2a0d632bad4df.png

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

通过上面的源码分析,我们可以得到下面的流程图:

确保了线程复用时,能够获取到线程执行前最新的数据,而不用担心上一次执行后留下的脏数据结果

044b77154f1df1ae17e3d4d490cdf9cd.png

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:

297dc70ae7f984a5497b7323a53a626a.png

里面有一个类:

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的注入&拦截应用

标签: 安全

本文转载自: https://blog.csdn.net/qq_29166327/article/details/142770420
版权归原作者 后台技术汇 所有, 如有侵权,请联系我们删除。

“线程安全反思录(上):ThreadLocal到底安全不?”的评论:

还没有评论