当前位置: 首页 > 后端技术 > Java

ThreadLocal源码分析与实际应用

时间:2023-04-01 17:56:11 Java

作者:京东物流闫鹏博1什么是ThreadLocal?ThreadLocal是一个创建线程局部变量的类。通常,我们创建的变量可以被任何线程访问和修改。使用ThreadLocal创建的变量只能被当前线程访问,不能被其他线程访问和修改。ThreadLocal最初是为了提供解决并发问题的解决方案而设计的。每个线程维护自己的数据,达到线程隔离的效果。2它有什么作用?2.1一次设置,到处搞定在目前的系统设计中,前后端分离已经基本成为常态。分离之后,如何获取用户信息就成了一个麻烦事。通常,用户登录后,用户信息会保存在Session或Token中。这时候,如果我们用常规手段去获取用户信息,就会非常困难。以Session为例,我们需要在接口参数中添加HttpServletRequest对象,然后调用getSession方法,每一个需要用户信息的接口都要添加。只有这个参数才能拿到Session,实现起来很麻烦。在实际的系统设计中,我们肯定不会使用上面提到的方法,而是使用ThreadLocal,我们会选择在拦截器业务中获取保存的用户信息,然后存储到ThreadLocal中,那么当前线程如果需要获取任何地方的用户信息,可以使用ThreadLocal的get()方法(ThreadLocal在异步程序中是不可靠的)2.2线程安全,空间换时间在Spring的Web项目中,我们通常将业务分为Controllers层,Service层,Dao层,大家都知道即@Autowired注解默认使用的是单例模式,那么不同的请求线程进来后,由于Dao层使用的是单例,所以只有一个Connection负责数据库连接,如果每个请求线程都要连接数据库,它会造成线程不安全。Spring是如何解决这个问题的?Spring项目中Dao层组装的Connection必须是线程安全的。解决方案是使用ThreadLocal方法。每个请求线程使用Connection时,都会从ThreadLocal中获取一次。如果为null,表示没有建立数据库连接,连接后保存在ThreadLocal中,这样每个请求线程都有自己保存的Connection。所以解决了线程安全的问题3ThreadLocal实际应用3.1在ehr中的使用在登录拦截器中写入用户信息,方便后续使用?为什么可以直接获取?对象存储在哪里?问题是什么?4.1get方法在get()方法中,还会获取到当前线程的ThreadLocalMap。如果ThreadLocalMap不为null,则获取的key为当前ThreadLocal的值;否则,调用setInitialValue()方法返回初始值,保存到新创建的ThreadLocalMap中。4.2set方法调用set时,直接调用set(Tvalue)方法,先获取当前线程,然后获取当前线程的ThreadLocalMap,如果ThreadLocalMap不为null,则将value保存到ThreadLocalMap中,并使用当前ThreadLocal作为键;否则,创建一个ThreadLocalMap交给当前线程,然后保存值。ThreadLocalMap相当于一个HashMap,它是实际保存值的map的集合。如果地图为空,则创建一个4.3initialValue()方法。initialValue()是ThreadLocal的初始值,默认返回null。子类可以覆盖该方法。用于设置ThreadLocal的初始值。4.4remove()方法ThreadLocal还有一个remove()方法,可以移除当前ThreadLocal对应的值。同样使用当前线程的ThreadLocalMap来移除对应的值。getMap得到了什么?在set、get、initialValue和remove方法中会获取当前线程,然后通过当前线程获取ThreadLocalMap。如果ThreadLocalMap为null,则会创建一个ThreadLocalMap并提供给当前线程。这里是Thread,可以直接点击“”获取这张图。每个Thread对象都维护了一个ThreadLocalMap这样一个ThreadLocalMap,里面可以存储若干个ThreadLocal。当使用ThreadLocal类型变量进行相关操作时,会通过当前线程获取ThreadLocalMap来完成操作。每个线程的ThreadLocalMap属于线程本身,ThreadLocalMap中维护的值也属于线程本身。这样可以保证ThreadLocal类型的变量在每个线程中都是独立的,在多线程环境下不会相互影响。5使用注意事项1)可能会造成内存泄漏。使用后,您需要将其移除。在ThreadLocalMap的set()、get()、remove()方法中,有清除无效Entry的操作。这样做是为了减少可能发生的内存泄漏。Entry中的键使用弱引用。这样做是为了降低内存泄漏的概率,但是并不能完全避免内存泄漏。假设Entry的key没有使用弱引用,而是使用了强引用:由于ThreadLocalMap的生命周期与当前线程一样长,当引用ThreadLocal的对象被回收时,由于ThreadLocalMap仍然持有ThreadLocal的强引用,而相应的值References,ThreadLocal和相应的值不会被回收,从而导致内存泄漏。所以Entry使用弱引用来避免ThreadLocal没有被回收导致内存泄漏,但是此时value仍然无法被回收,仍然会造成内存泄漏。ThreadLocalMap已经考虑到了这种情况,有一些保护措施:在调用ThreadLocal的get()、set()和remove()时,会清除当前线程ThreadLocalMap中所有key为null的值。这减少了发生内存泄漏的机会。所以我们在使用ThreadLocal的时候,每次ThreadLocal用完了都会调用remove()方法来清除数据,防止内存泄露。2)使用线程池时,慎用父子线程传递,因为初始化时间是线程创建的时候。3)2有解决办法吗?TransmittableThreadLocal源码地址:https://github.com/alibaba/tr...详细解释:https://www.jianshu.com/p/e07...