ThreadLocal
类是Java提供的一种线程局部存储,它为每个使用该变量的线程提供一个独立的变量副本。这意味着,每个线程可以访问自己内部的ThreadLocal
变量,而不会和其他线程的ThreadLocal
变量冲突。ThreadLocal
实例通常被声明为private static
类型。总结来说,ThreadLocal
不是为了解决多线程访问共享变量的问题,而是为每个线程创建一个单独的变量副本,提供保持对象的方法和避免参数传递的复杂性。
ThreadLocal
可以看做是一个容器,容器里面存放着属于当前线程的变量。ThreadLocal
类提供了四个对外开放的接口方法,这也是用户操作ThreadLocal
类的基本方法:
void set(Object value)
:设置当前线程的线程局部变量的值。public Object get()
:该方法返回当前线程所对应的线程局部变量。public void remove()
:将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。protected Object initialValue()
:返回该线程局部变量的初始值,该方法是一个protected
的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()
或set(Object)
时才执行,并且仅执行1次,ThreadLocal
中的缺省实现直接返回一个null
。ThreadLocal
内部是如何为每一个线程维护变量副本的呢?其实在ThreadLocal
类中有一个静态内部类ThreadLocalMap
(其类似于Map
),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap
中元素的key
为当前ThreadLocal
对象,而value
对应线程的变量副本,每个线程可能存在多个ThreadLocal
。
每个线程中都存在一个map
,map
的类型是ThreadLocal.ThreadLocalMap
。Map
中的key
为一个threadLocal
实例,这个Map
的确使用了弱引用,不过弱引用只是针对key
。每个key
都弱引用指向threadLocal
。当把threadLocal
实例置为null
以后,没有任何强引用指向threadLocal
实例,所以threadLocal
将会被gc
回收。但是,我们的value
却不能回收,因为存在一条从current thread
连接过来的强引用。只有当前线程结束后,current thread
就不会存在栈中,强引用断开,Current Thread
, Map
, value
将全部被GC
回收。
所以得出一个结论就是只要这个线程对象被gc
回收,就不会出现内存泄露,但在threadLocal
设为null
和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的就可能出现内存泄露(在web应用中,每次http请求都是一个线程,tomcat容器配置使用线程池时会出现内存泄漏问题)。
ThreadLocalMap
是ThreadLocal
的静态内部类,它是每个线程的内部属性,其生命周期与线程相同。ThreadLocalMap
使用ThreadLocal
对象作为键(key),这些键实际上是弱引用,而它们所对应的值(value)则是强引用。这意味着,如果一个ThreadLocal
对象没有被外部强引用所引用,那么它可能会在下一次垃圾回收时被回收,即使线程仍然存活。然而,它所对应的值(value)仍然会占用内存,因为它有一个强引用从Thread
对象指向ThreadLocalMap
,再从ThreadLocalMap
指向这些值。
ThreadLocalMap
的初始容量为16,当容量超过2/3时会自动扩容。这个扩容操作与HashMap
的扩容操作类似,但ThreadLocalMap
做了一些优化,比如在扩容时,它不会像HashMap
那样保留原有的哈希值,而是重新计算每个键的哈希值。
ThreadLocal
变量声明为private static
,这样可以确保ThreadLocal
实例在整个应用程序中是唯一的。ThreadLocal
变量后,应该调用remove()
方法来清除当前线程的ThreadLocalMap
中的条目,避免内存泄漏。ThreadLocal
变量可能会导致内存泄漏。因此,在finally块中调用remove()
方法是一个好习惯。ThreadLocal
是一个强大的工具,它允许我们为每个线程存储和管理自己的数据。然而,如果不正确地使用它,可能会导致内存泄漏,特别是在使用线程池的情况下。为了避免这些问题,我们应该在不再需要ThreadLocal
变量时调用remove()
方法,以确保及时清理资源。同时,理解ThreadLocal
的内部工作原理和内存泄漏的原因,可以帮助我们更好地使用这个工具,并避免潜在的问题。
通过上述内容,我们深入了解了ThreadLocal
的工作原理、内存泄漏问题以及如何正确使用ThreadLocal
。希望这些信息能够帮助您在实际开发中更好地利用ThreadLocal
,同时避免可能的问题。