关于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/ ...