作者:京东物流 闫鹏勃

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...