threadlocal源码分析(ThreadLocal源码解析及实战应用)
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 AopContext4 源码解读
你是否有这样的疑惑?为什么可以直接拿到?对象存放在哪里?存在什么问题?
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/transmittable-thread-local
详解:https://www.jianshu.com/p/e0774f965aa3创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!