乐趣区

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

https://blog.csdn.net/u010445301/article/details/111322569

作者:京东批发 李泽阳

起源:京东云开发者社区 转载请注明起源

退出移动版