ThreadLocal使用场景
ThreadLocal原理
在每个线程Thread内部有一个ThreadLocalMap,这是用来存储实际的变量副本的,键值key为当前ThreadLocal变量,value为变量副本。初始时,在Thread里面,ThreadLocalMap为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的ThreadLocalMap进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到ThreadLocalMap。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在ThreadLocalMap里面查找。 一个Thread中只有一个ThreadLocalMap,一个ThreadLocalMap中可以有多个ThreadLocal对象,其中一个ThreadLocal对象对应一个ThreadLocalMap中的一个Entry(即一个Thread可以依附有多个ThreadLocal对象)。
ThreadLocal变量的存在周期
存储在ThreadLocal中的对象将一直附在该线程,直到显式删除为止.
正如javadoc中所说:
只要线程是活动的并且线程本地实例是可访问的,那么每个线程都持有对其线程本地变量副本的隐式引用。在线程消失之后,它的所有线程本地实例副本都将进入垃圾收集(除非存在对这些副本的其他引用)。
例如,如果您的服务在servlet容器中执行,那么当请求完成时,它的线程将返回到池中。如果您还没有清理线程的ThreadLocal变量内容,那么在线程处理下一个请求时该数据将继续存在。每个线程都是GC根节点,附加到线程的线程本地变量在线程结束后才会被垃圾回收。
ThreadLocal变量的清理
你可能希望为线程池中的线程清理线程本地变量,原因有两个:
threadlocal内存泄漏通常在有界线程池中不是一个大问题,因为如果使用静态变量来保存threadlocal单例实例,threadlocal变量在线程被再次使用时最终都可能被覆盖,在线程池中,每个线程只泄漏(最多)一个实例(一个thread local值);但是,如果你不使用静态变量保存单例实例,程序可能会一次又一次地创建新ThreadLocal实例,线程本地值不会被覆盖,并且会在每个线程的threadlocal map中累积。这可能会导致严重的泄漏。即使ThreadLocal实例被回收,其关联的变量依然存在,看网上一幅图
图片来源:http://images2015.cnblogs.com/blog/500720/201509/500720-20150918023734289-147214397.png
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。JAVA的ThreadLocal对Key使用到了弱引用,但是为了保证不再内存泄露,在每次set.get的时候主动对key==null的entry做遍历回收。虽然不会造成内存泄露,但是因为只有在每次set,get的时候才会对entry做key==null的判断,从而释放内存,这并不能保证ThreadLocal不会发生内存泄漏,例如:
如果需要,您需要自己处理线程局部变量。唯一彻底的方法是调用ThreadLocal.remove()方法。要执行清理,通常需要确定线程在当前处理中完成的位置。例如,在servlet filter中,可以在线程返回到线程池之前删除threadlocal变量。您一般不会使用try-finally块,因为插入threadlocal对象的位置与清理它的位置相去甚远。假设您正在ka在webapp处理HTTP请求时创建/使用的线程局部变量,避免线程本地泄漏的一种方法是在webapp的ServletContext中注册一个ServletRequestListener,并实现该侦听器的requestDestroyed方法来清除当前线程的线程本地。注意,在这种情况下,您还需要考虑信息从一个请求泄漏到另一个请求的可能性。