作者:京东物流 闫鹏勃
1 什么是 ThreadLocal?
ThreadLocal 是一个对于创立线程局部变量的类。
通常状况下,咱们创立的变量是能够被任何一个线程拜访并批改的。而应用 ThreadLocal 创立的变量只能被以后线程拜访,其余线程则无法访问和批改。ThreadLocal 在设计之初就是为解决并发问题而提供一种计划,每个线程保护一份本人的数据,达到线程隔离的成果。
2 有什么作用?
2.1 set once,get everywhere
在当初的零碎设计中,前后端拆散已根本成为常态,拆散之后如何获取用户信息就成了一件麻烦事,通常在用户登录后,用户信息会保留在 Session 或者 Token 中。这个时候,咱们如果应用惯例的伎俩去获取用户信息会很吃力,拿 Session 来说,咱们要在接口参数中加上 HttpServletRequest 对象,而后调用 getSession 办法,且每一个须要用户信息的接口都要加上这个参数,能力获取 Session,这样实现就很麻烦了。
在理论的零碎设计中,咱们必定不会采纳下面所说的这种形式,而是应用 ThreadLocal,咱们会抉择在拦截器的业务中,获取到保留的用户信息,而后存入 ThreadLocal,那么以后线程在任何中央如果须要拿到用户信息都能够应用 ThreadLocal 的 get() 办法 ( 异步程序中 ThreadLocal 是不牢靠的)
2.2 线程平安,空间换工夫
在 Spring 的 Web 我的项目中,咱们通常会将业务分为 Controller 层,Service 层,Dao 层,咱们都晓得 @Autowired 注解默认应用单例模式,那么不同申请线程进来之后,因为 Dao 层应用单例,那么负责数据库连贯的 Connection 也只有一个,如果每个申请线程都去连贯数据库,那么就会造成线程不平安的问题,Spring 是如何解决这个问题的呢?
在 Spring 我的项目中 Dao 层中拆卸的 Connection 必定是线程平安的,其解决方案就是采纳 ThreadLocal 办法,当每个申请线程应用 Connection 的时候,都会从 ThreadLocal 获取一次,如果为 null,阐明没有进行过数据库连贯,连贯后存入 ThreadLocal 中,如此一来,每一个申请线程都保留有一份 本人的 Connection。于是便解决了线程平安问题
3 ThreadLocal 实战利用
3.1 ehr 中的应用
在登录拦截器中将用户信息写入,后续应用时不便取值
3.2 分页插件 PageHelper 中的利用
3.3 AopContext
4 源码解读
你是否有这样的纳闷? 为什么能够间接拿到?对象寄存在哪里? 存在什么问题?
4.1 get 办法
在 get() 办法中也会获取到以后线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则把获取 key 为以后 ThreadLocal 的值;否则调用 setInitialValue() 办法返回初始值,并保留到新创建的 ThreadLocalMap 中。
4.2 set 办法
调用 set 时,间接调用 set(T value) 办法中,首先获取以后线程,而后在获取到以后线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则将 value 保留到 ThreadLocalMap 中,并用以后 ThreadLocal 作为 key;否则创立一个 ThreadLocalMap 并给到以后线程,而后保留 value。
ThreadLocalMap 相当于一个 HashMap,是真正保留值的中央
map 的 set,如果 map 为空,则创立一个
4.3 initialValue() 办法
initialValue() 是 ThreadLocal 的初始值,默认返回 null,子类能够重写改办法,用于设置 ThreadLocal 的初始值。
4.4 remove() 办法
ThreadLocal 还有一个 remove() 办法,用来移除以后 ThreadLocal 对应的值。同样也是同过以后线程的 ThreadLocalMap 来移除相应的值。
getMap 拿到了什么?
在 set,get,initialValue 和 remove 办法中都会获取到以后线程,而后通过以后线程获取到 ThreadLocalMap,如果 ThreadLocalMap 为 null,则会创立一个 ThreadLocalMap,并给到以后线程
此处 t 是 Thread,间接能够“点”拿到这个 map
每个 Thread 对象外部都保护了一个 ThreadLocalMap 这样一个 ThreadLocal 的 Map,能够寄存若干个 ThreadLocal
在应用 ThreadLocal 类型变量进行相干操作时,都会通过以后线程获取到 ThreadLocalMap 来实现操作。每个线程的 ThreadLocalMap 是属于线程本人的,ThreadLocalMap 中保护的值也是属于线程本人的。这就保障了 ThreadLocal 类型的变量在每个线程中是独立的,在多线程环境下不会相互影响。
5 应用注意事项
1)有可能导致内存透露,应用结束后,须要 remove
在 ThreadLocalMap 的 set(),get() 和 remove() 办法中,都有革除有效 Entry 的操作,这样做是为了升高内存透露产生的可能。
Entry 中的 key 应用了弱援用的形式,这样做是为了升高内存透露产生的概率,但不能完全避免内存透露。
假如 Entry 的 key 没有应用弱援用的形式,而是应用了强援用:因为 ThreadLocalMap 的生命周期和以后线程一样长,那么当援用 ThreadLocal 的对象被回收后,因为 ThreadLocalMap 还持有 ThreadLocal 和对应 value 的强援用,ThreadLocal 和对应的 value 是不会被回收的,这就导致了内存透露。所以 Entry 以弱援用的形式防止了 ThreadLocal 没有被回收而导致的内存透露,然而此时 value 依然是无奈回收的,仍然会导致内存透露。
ThreadLocalMap 曾经思考到这种状况,并且有一些防护措施:在调用 ThreadLocal 的 get(),set() 和 remove() 的时候都会革除以后线程 ThreadLocalMap 中所有 key 为 null 的 value。这样能够升高内存透露产生的概率。所以咱们在应用 ThreadLocal 的时候,每次用完 ThreadLocal 都调用 remove() 办法,革除数据,避免内存透露。
2)应用线程池时,父子线程传递慎用,因为初始化机会为线程创立时
3)针对 2 有什么计划能够解决?
TransmittableThreadLocal
源码地址:https://github.com/alibaba/tr…
详解:https://www.jianshu.com/p/e07…