前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解ThreadLocal

深入理解ThreadLocal

作者头像
java达人
发布2019-03-08 14:31:00
9380
发布2019-03-08 14:31:00
举报
文章被收录于专栏:java达人java达人

ThreadLocal使用场景

  1. 每个线程都需要维护一个自己专用的线程的上下文变量,比如jdbc连接,web应用中的session等。
  2. 包装一个线程不安全的成员变量,给其提供一个线程安全的环境,同时避免对该对象的同步访问(synchronized),比如Java里面的SimpleDateFormat是线程不安全的,所以在多线程下使用可以采用ThreadLocal包装,从而提供安全的访问。
  3. 对于一些线程级别,传递方法参数有许多层的时候,我们可以使用ThreadLocal包装,只在特定地方set一次,然后不管在什么地方都可以随便get出来,从而巧妙了避免了多层传参。如果上下文信息的范围仅限于一个service,那么最好通过方法参数传递信息,而不是使用ThreadLocal。ThreadLocal适用于需要跨不同服务或在不同层中传递提供信息的情况,如果只被一个service使用,那么你的代码就会显得过于复杂。如果你有数据被AOP advice在不同对象使用,那么将这些数据放到threadlocal中可能是一种不错的方法。

ThreadLocal原理

在每个线程Thread内部有一个ThreadLocalMap,这是用来存储实际的变量副本的,键值key为当前ThreadLocal变量,value为变量副本。初始时,在Thread里面,ThreadLocalMap为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的ThreadLocalMap进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到ThreadLocalMap。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在ThreadLocalMap里面查找。 一个Thread中只有一个ThreadLocalMap,一个ThreadLocalMap中可以有多个ThreadLocal对象,其中一个ThreadLocal对象对应一个ThreadLocalMap中的一个Entry(即一个Thread可以依附有多个ThreadLocal对象)。

ThreadLocal变量的存在周期

存储在ThreadLocal中的对象将一直附在该线程,直到显式删除为止.

正如javadoc中所说:

只要线程是活动的并且线程本地实例是可访问的,那么每个线程都持有对其线程本地变量副本的隐式引用。在线程消失之后,它的所有线程本地实例副本都将进入垃圾收集(除非存在对这些副本的其他引用)。

例如,如果您的服务在servlet容器中执行,那么当请求完成时,它的线程将返回到池中。如果您还没有清理线程的ThreadLocal变量内容,那么在线程处理下一个请求时该数据将继续存在。每个线程都是GC根节点,附加到线程的线程本地变量在线程结束后才会被垃圾回收。

ThreadLocal变量的清理

你可能希望为线程池中的线程清理线程本地变量,原因有两个:

  • 防止内存(或其他资源)泄漏。
  • 防止信息通过threadlocal从一个请求意外泄漏到另一个请求。

threadlocal内存泄漏通常在有界线程池中不是一个大问题,因为如果使用静态变量来保存threadlocal单例实例,threadlocal变量在线程被再次使用时最终都可能被覆盖,在线程池中,每个线程只泄漏(最多)一个实例(一个thread local值);但是,如果你不使用静态变量保存单例实例,程序可能会一次又一次地创建新ThreadLocal实例,线程本地值不会被覆盖,并且会在每个线程的threadlocal map中累积。这可能会导致严重的泄漏。即使ThreadLocal实例被回收,其关联的变量依然存在,看网上一幅图

图片来源:http://images2015.cnblogs.com/blog/500720/201509/500720-20150918023734289-147214397.png

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。JAVA的ThreadLocal对Key使用到了弱引用,但是为了保证不再内存泄露,在每次set.get的时候主动对key==null的entry做遍历回收。虽然不会造成内存泄露,但是因为只有在每次set,get的时候才会对entry做key==null的判断,从而释放内存,这并不能保证ThreadLocal不会发生内存泄漏,例如:

  • 使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏。
  • 分配使用了ThreadLocal又不再调用get()、set()、remove()方法,那么就会导致内存泄漏。

如果需要,您需要自己处理线程局部变量。唯一彻底的方法是调用ThreadLocal.remove()方法。要执行清理,通常需要确定线程在当前处理中完成的位置。例如,在servlet filter中,可以在线程返回到线程池之前删除threadlocal变量。您一般不会使用try-finally块,因为插入threadlocal对象的位置与清理它的位置相去甚远。假设您正在ka在webapp处理HTTP请求时创建/使用的线程局部变量,避免线程本地泄漏的一种方法是在webapp的ServletContext中注册一个ServletRequestListener,并实现该侦听器的requestDestroyed方法来清除当前线程的线程本地。注意,在这种情况下,您还需要考虑信息从一个请求泄漏到另一个请求的可能性。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-02-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档