从Java官方文档中的描述:ThreadLoacl类用来提供线程内部的局部变量,这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static 类型的,用于关联线程和线程上下文。
ThreadLocal的主要作用就是讲数据放入到当前线程对象中的Map中,这个Map是Thread类的实例变量。类ThreadLocal自己不管理,不存储任何数据,他只是数据和Map之间的桥梁,用于讲数据放入Map中。执行后每个线程中的Map存有自己的数据,Map中的key存储的是ThreadLocal对象,value就是存储的值,每个Thread中的Map值只对当前线程可见,当前线程销毁,Map随之销毁,Map中的数据如果没有被引用,没有被使用,则随时被GC收回。
我们可以得知ThreadLoacl的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的声明周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
总结:1:线程并发:在多线程并发的情境下。
2:传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量。
3:线程隔离:每个线程的变量都是独立的,不会相互影响。
虽然ThreadLocal模式与synchronized关键字都是用于处理多线程并发访问变量的问题,不过两者处理问题的加角度和思路不同。
ThreadLoacl:原理:ThreadLocal采用以空间换时间的方式,为每一个线程都提供了一份变量的副本。从而实现同时访问而相不干扰。
侧重点:多线程中每个线程之间的数据隔离
synchronized:原理:同步机制采用以时间换空间的方式,只提供一份变量,让不同的线程排队访问。
侧重点:多个线程之间访问资源的同步。
总结:在刚刚的案例中,虽然使用ThreadLocal和synchronized都能解决问题,但是使用ThreadLocal更为合适,因为这样可以使程序拥有更高的并发性。
三:ThreadLocal内部结构设计
每个ThreadLocal维护一个TheadLocalMap.这个Map是ThreadLocal实例本身,value才是真正要存储的值Object。
A:每个ThreadLoacal线程内部都有一个Map(ThreadLocalMap)
B:Map里面存储ThreadLocal对象(key)和线程的变量副本
C:Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值
D:对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互补干扰。
ThreadLocal底层代码中在set时,new了一个Entry,这个Entry继承WeakReference,并且用ThreadLocal作为key,如果key为null,意味着key不在被引用,因此这时候entry可以从table中清除。
四:软引用和内存泄漏
1:内存泄漏(Memeory leak):内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序隐形速度减慢甚至系统崩溃等严重后果,内存泄漏的堆积终讲导致内存溢出。
2:软引用(weakReference):垃圾回收器一旦发现了只具有软引用的对象,不管当前内存空间是否足够,都回收他的内存。
A:如果key使用强引用:
假如ThreadLocalMap中的key使用了强引用,那么会出现内存泄漏吗?
3:那么ThreadLocalMap中的key使用了软引用,会出现内存泄漏吗?
A:假如在业务代码中使用完ThreadLocal,threadLocal Ref被引用了
B:由于ThreadLocalMap中持有ThreadLocal的软引用,没有任何强引用指向threadLocal实例,所以threadLocal就可以顺利被gc回收,此时Entry中的key=null。
C:但是在没有手动删除这个Entry以及CurrentThread依然运行的前提下,也存在有强引用链threadRef->currentThread->threadLocalMap->entry-value,value不会被回收,而这块value永远不会被访问到了,导致value内存泄漏
也就是说,ThreadLocalMap中key使用了软引用,也有可能内存泄漏。
五:为什么使用软引用
1:无论使用ThreadLocal中的key使用哪种类型引用都无法完全避免内存泄漏,跟使用软引用没有关系。
避免内存泄漏的两种方式:
A:使用完ThreadLoacl,调用remove方法删除对应的Entry.
B:使用完ThreadLocal,当前Thread也随之运行结束
但第二种方式不好控制,特别是使用线程池的时候,线程结束是不会销毁的。
事实上,在ThreadLocalMap中的set/getEntry方法中,会对key未nul进行判断,如果为null的话,那么是会对value置为null的。
这就意味着使用完ThreadLocal,CurrentThread依然运行的前提下,就算是忘记调用remove方法,软引用比强引用可以多一层保障,软引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap调用set,get,remove的任一方法的时候会被清除,从而避免内存泄漏。