关于threadlocal:ThreadLocal线程中的全局变量-京东云技术团队

最近接了一个新需要,业务场景上须要在原有根底上新增2个字段,接口新增参数意味着很多类和办法的逻辑都须要扭转,须要先判断是否属于该业务场景,再做对应的逻辑。本来的打算是在入口处新增变量,在操作数据的时候进行逻辑判断将变量进行存储或查问。 如果全链路都变更入参和构造,很显著代码上很不优雅,后续如果还要减少业务场景,又须要再改一遍。如果有一个办法能够传递全局变量,而且仅限于以后线程就好了。 到此,会想到有两种解决方案:之前用的比拟少的ThreadLocal或者应用redis缓存。思考到新增字段都是些增删改查的操作,没有必要存到redis中,故应用ThreadLocal。 一、ThreadLocal定义以微服务架构为例,服务提供方在收到调用方的申请后,会把这个申请调配给一个线程进行解决。一般来说,一个申请会始终由同一个线程解决,两头不会切换线程,所以如果有一个线程中共享的变量,能够当全局变量应用。 ThreadLocal实现的就是一个线程中的全局变量,与真正的全局变量的区别在于ThreadLocal的变量是每个线程中的全局变量,也就是说不同线程拜访到的值是不一样的。其填充的变量属于以后线程,该变量对于其余线程是隔离的。 由定义能够发现,ThreadLocal有两个个性:每个Thread的变量只能由以后Thread应用;因为其余线程不可拜访,则不存在多线程间共享的问题。 二、润饰ThreadLocal提供了线程本地的实例,它与一般变量的区别在于,每个应用该变量的线程都会初始化一个齐全独立的实例正本。 ThreadLocal变量通常被private static润饰,这样的益处是当一个线程完结时,它所应用的ThreadLocal实例正本都可被回收,防止反复创立。害处就是这样做可能正好导致内存透露。 三、底层实现ThreadLocal最奢侈的外部实现是Map<threadlocal, Object>,这是一个HashMap,又称为ThreadLocalMap。但Java源码并不是Map<threadlocal, Object>的实现。这是因为如果多个线程拜访同一个map,这个map须要是线程平安的,结构比拟麻烦。Java采纳了更简略粗犷的做法:每个线程都有本人的ThreadLocal专属map,外面能够寄存多个ThreadLocal变量,这样就解决了多线程同时操作一个map带来的多线程并发问题。 因为要把ThreadLocal的变量当做全局变量应用,须要把变量与初始化函数写在通用的类中,如DDD畛域模型中写在Common模块。 具体的实现如下: public class ThreadLocalUtil { private static ThreadLocal<Integer> THREAD_LOCAL = new ThreadLocal<>(); public static Integer getScene() { return THREAD_LOCAL.get(); } public static void initScene(Integer scene) { if (THREAD_LOCAL == null) { THREAD_LOCAL = new ThreadLocal<>(); } THREAD_LOCAL.set(scene); } public static void remove() { THREAD_LOCAL.remove(); }}四、致命点下面提到了的ThreadLocal会带来内存泄露的问题,深入分析下: 一个ThreadLocal实例对该当火线程的一个对象实例,如果把ThreadLocal申明为某个类的实例变量不是动态变量,那么每次创立一个该类的实例就会导致一个新的对象实例被创立。而这些被创立的实例是同一个类的实例,于是同一个线程可能会拜访到同一个类的不同实例,这即便不会导致谬误,也会导致反复创立同样的对象。如果应用static润饰后,只有相应的类没有被垃圾回收掉,那么这个类就会持有绝对应的ThreadLocal实例援用。 ThreadLocal本身并不存储值,而是作为一个key来让线程从ThreadLocal中获取value。ThreadLocalMap中的key是弱援用,所以jvm在垃圾回收时如果内部没有强援用来援用它,ThreadLocal必然会被回收。然而,作为ThreadLocalMap中的key,ThreadLocal被回收后,ThreadLocalMap就会存在null,但value却不为null。如果以后线程始终不完结或者线程完结后不被你销毁,这会产生内存泄露(已调配空间的堆内存因为某种原因未开释或无奈开释导致系统内存节约或程序运行变慢甚至零碎奔溃)。 因而,key弱援用并不是导致内存泄露的起因,而是因为ThreadLocalMap的生命周期与以后线程一样长,并且没有手动删除对应的value。 解决的办法也很简略,只须要突破援用门路中的ThreadLocalMap对对象实例的援用即可。也就是在应用完ThreadLocal之后,必须调用ThreadLocal.remove()。 延长:为什么要将Map中的key设置为弱援用呢? 实际上,设置key为弱援用能预防大多数内存泄露的状况。如果key应用强援用,援用的ThreadLocal对象被回收,然而ThreadLocalMap还持有ThreadLocal的强援用,如果没有手动删除,ThreadLocal不会被回收,也会导致内存泄露。设置为弱援用后,援用的ThreadLocal对象被回收,因为ThreadLocalMap持有ThreadLocal的弱援用,即便没有手动删除,ThreadLocal也会被java GC回收。value在下一次ThreadLocalMap调用set、get、remove的时候会被革除。 参考文章: https://www.cnblogs.com/tiancai/p/13141234.html?ivk_sa=1024320u https://last2win.com/2020/09/05/java-threadlocal/ ...

September 4, 2023 · 1 min · jiezi

关于threadlocal:记一次线程池配置导致的ThreadLocal清空

1. 景象某天服务器监控上发现大量接口报错,查看服务器日志,并且剖析代码后,发现最间接的问题景象有: Controller 的主线程中,RequestContextHolder.getRequestAttributes() 返回的值,会忽然在某个时刻返回的是 null,从而导致API的逻辑报错。elk日志中,spring cloud sleuth 框架外面的 traceId,会在接口解决链路的某个环节忽然失落了,只有新建的spanId。elk日志中,最大量的报错日志信息,都是在于异步申请一个第三方服务 timeout,然而申请这个第三方服务是通过线程池异步执行的。后面1、2 两个问题临时无思路,因为通过日志来看,问题呈现的环节无规律,也摸不着头脑。但第3个问题则是很明确,那个第三方服务不可用了。但问题3毕竟是异步线程执行的,原则上来讲不应该影响主线程,虽说临时看不到和第1、2问题的关联点,可教训上来看应该脱不了干系。 于是咱们将对那个第三方服务的申请熔断了。后果是缓缓的,服务器谬误逐步缩小,直到最终恢复正常。 当初根本能够必定,问题1、2 是受问题3影响的。接下来就须要查清问题产生的原委。 2. 排查进一步剖析问题1、2,就能显著看进去,这和线程无关。无论 RequestContextHolder 还是 MDC,都是基于 ThreadLocal 实现的,上述乱象外表,应该是本地线程 ThreadLocal 被动了。可到底是 ThreadLocal 的值被动了?还是以后线程被切换了呢?目前只能从问题3着手。 剖析问题3的景象,过后第三方服务不可用,当调用服务不是间接被回绝,而且在申请达到最大 timeout 限度后报错。虽说整个过程是基于线程池异步执行,可这类申请 timeout 的景象,对线程池带来的后果是灾难性的。 因为线程池中调配的线程在解决 timeout 申请时,也只能放弃期待,等到超时工夫后才回收线程。因为是异步执行的,tomcat 线程池的解决效率并不会受影响,一直的有API申请涌入尽量,并给业务线程池调配调用第三方服务的工作。而业务线程池下执行工作的每个线程因为 timeout 申请阻塞,会导致短时间内并发线程数量迅速飙升。 咱们再着重搜一下出问题时,线程池的告警日志,果然,过后线程池曾经打满,开始大量执行回绝策略。 再看看咱们业务线程池的配置。这个线程池的配置在我后面几篇文章中呈现过,之前认为是比拟规范的,但这次发现有很大的坑。 @Bean("customExecutor") public Executor getAsyncExecutor() { final RejectedExecutionHandler rejectedHandler = new ThreadPoolExecutor.CallerRunsPolicy() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { log.warn("LOG:线程池容量不够,思考减少线程数量,但更举荐将线程耗费数量大的程序应用独自的线程池"); super.rejectedExecution(r, e); } }; ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); threadPoolTaskExecutor.setCorePoolSize(7); threadPoolTaskExecutor.setMaxPoolSize(42); threadPoolTaskExecutor.setQueueCapacity(11); threadPoolTaskExecutor.setRejectedExecutionHandler(rejectedHandler); threadPoolTaskExecutor.setThreadNamePrefix("Custom Executor-"); threadPoolTaskExecutor.setTaskDecorator(runnable -> { try { Optional<RequestAttributes> requestAttributesOptional = ofNullable(RequestContextHolder.getRequestAttributes()); Optional<Map<String, String>> contextMapOptional = ofNullable(MDC.getCopyOfContextMap()); return () -> { try { requestAttributesOptional.ifPresent(RequestContextHolder::setRequestAttributes); contextMapOptional.ifPresent(MDC::setContextMap); runnable.run(); } finally { MDC.clear(); RequestContextHolder.resetRequestAttributes(); } }; } catch (Exception e) { return runnable; } }); return threadPoolTaskExecutor; }3. 论断咱们着重关注线程池的两个参数配置: ...

November 20, 2022 · 1 min · jiezi

关于threadlocal:说实话ThreadLocal真不是啥高级的东西

什么是ThreadLocal?从 Java 官网文档中的形容:ThreadLocal 类用来提供线程外部的局部变量。这种变量在多线程环境下拜访(通过get 和 set 办法拜访)时能保障各个线程的变量绝对独立于其余线程内的变量。ThreadLocal 实例通常来说都是 private static 类型的,用于关联线程和线程上下文。 咱们能够得悉 ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会互相烦扰,这种变量在线程的生命周期内起作用,缩小同一个线程内多个函数或组件之间一些公共变量传递的复杂度。 线程并发:在多线程并发的场景下传递数据:咱们能够通过 ThreadLocal 在同一线程,不同组件之间传递公共变量(有点相似于 Session?)线程隔离:每个线程的变量都是独立的,不会相互影响根本应用在介绍 ThreadLocal 应用之前,咱们首先意识几个 ThreadLocal 的常见办法 应用案例咱们来看上面这个线程不平安的案例,感受一下 ThreadLocal 线程隔离的特点。 /** * 需要:线程隔离 * 在多线程并发的场景下,每个线程中的变量都是互相独立的 * 线程A:设置变量1,获取变量2 * 线程B:设置变量2,获取变量2 * @author: 陌溪 */public class MyDemo01 { // 变量 private String content; public String getContent() { return content; } public void setContent(String content) { this.content = content; } public static void main(String[] args) { MyDemo01 myDemo01 = new MyDemo01(); for (int i = 0; i < 5; i++) { new Thread(() -> { myDemo01.setContent(Thread.currentThread().getName() + "的数据"); System.out.println("-----------------------------------------"); System.out.println(Thread.currentThread().getName() + "\t " + myDemo01.getContent()); }, String.valueOf(i)).start(); } }}运行后的成果 ...

June 30, 2022 · 4 min · jiezi

关于threadlocal:阿里-TransmittableThreadLocal-代码简读

零 筹备0 FBI WARNING文章异样啰嗦且绕弯。 1 TransmittableThreadLocal 是什么当开发人员须要在线程池的线程中传递某些参数的时候,jdk 的 ThreadLocal 很难实现,动态变量则会面临不够灵便和呈现线程平安等问题。TransmittableThreadLocal 是阿里开源工具包,用于解决这一问题。 2 版本jdk 版本Azul JDK 17.0.2 transmittable-thread-local2.13.0-Beta1 junit-jupiter5.8.2 一 Demoimport com.alibaba.ttl.TransmittableThreadLocal;import com.alibaba.ttl.threadpool.TtlExecutors;import org.junit.jupiter.api.Test;import java.util.concurrent.Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TreadLocalTest { @Test public void transmittableThreadLocal() { TransmittableThreadLocal<Integer> tl = new TransmittableThreadLocal<>(); tl.set(6); System.out.println("父线程获取数据:" + tl.get()); // 第一次输入:6 // 应用 jdk 的 Executors 工具创立一个线程池 // 留神,这个线程池里只有一个线程 Executor realPool = Executors.newFixedThreadPool(1); // 应用 TtlExecutors 创立一个 Ttl 框架封装的线程池 Executor pool = TtlExecutors.getTtlExecutor(realPool); // 应用线程池跑一个工作 pool.execute(() -> { Integer i = tl.get(); System.out.println("第一次获取数据:" + i); // 第二次输入:6 }); // 批改一下 tl 里的值,并再跑一次工作 tl.set(7); pool.execute(() -> { Integer i = tl.get(); System.out.println("第二次获取数据:" + i); // 第三次输入:7 }); }}二 先从 InheritableThreadLocal 说起1 ThreadInheritableThreadLocal 是 jdk 中自带的 ThreadLocal 的子类,在 jdk 的 Thread 对象中,会对它有独自的反对。首先来看 Thread 的构造方法: ...

June 8, 2022 · 7 min · jiezi

关于threadlocal:知道ThreadLocal吗一起聊聊到底有啥用

摘要:ThreadLocal是 java 提供的一个不便对象在本线程内不同办法中传递和获取的类。用它定义的变量,仅在本线程中可见和保护,不受其余线程的影响,与其余线程互相隔离。本文分享自华为云社区《ThreadLocal:线程专属的变量》,作者: zuozewei 。 ThreadLocal 简介ThreadLocal是 java 提供的一个不便对象在本线程内不同办法中传递和获取的类。用它定义的变量,仅在本线程中可见和保护,不受其余线程的影响,与其余线程互相隔离。 那 ThreadLocal 到底解决了什么问题,又实用于什么样的场景? This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist). ...

August 3, 2021 · 5 min · jiezi