ThreadLocal 是 Java 提供的一个线程内部的存储类,可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
特点:
ThreadLocal 中存储的是线程隔离的数据,该数据只能被当前线程访问,其他线程无法访问和修改。
每个 ThreadLocal 对象只能被当前线程使用,其他线程无法访问。
每个线程中都可以独立地改变自己的 ThreadLocal 变量,不会影响其他线程的变量。
当线程结束时,ThreadLocal 中存储的对象会被垃圾回收。
实现原理:
ThreadLocal 是一个映射表,Key 是 ThreadLocal 实例本身,Value 是真正要存储的对象。每个线程都有一个独立的 ThreadLocalMap 副本。
每个 Thread 线程内部都有一个 ThreadLocalMap 的引用,这个映射表是用于存储实际的对象或值的。
当调用 ThreadLocal 的 set() 方法设置值的时候,先得到当前线程的 ThreadLocalMap,然后将 ThreadLocal 实例本身作为键,而要存储的对象作为值放入 ThreadLocalMap。
当调用 get() 方法获取值时,同样先获取当前线程的 ThreadLocalMap,然后以 ThreadLocal 实例作为键,从 ThreadLocalMap 获取对应的对象。
因为每个线程的 ThreadLocalMap 是隔离的,所以一个线程无法访问其他线程中的值,从而实现了线程隔离。
当线程结束时,线程的 ThreadLocalMap 也会随之销毁,避免了内存泄漏。
ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 没有外部强引用来引用它,那么系统 GC 的时候,这个 ThreadLocal 会被回收,这样就避免了 ThreadLocal 本身的内存泄漏。
使用场景:
频繁创建和销毁的对象,可以避免创建开销。
每个线程需要单独的实例,通常用于代理,事务等。
需要将共享资源的访问限制在同一个线程内,避免多线程访问导致竞争。
存储线程上下文数据,如用户身份信息等。
内存泄露可能原因
孤儿引用对象无法被GCThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用它,那么系统GC的时候,这个ThreadLocal势必会被回收,而它对应的value却有可能无法被回收,因为当前线程的ThreadLocalMap还持有该value的强引用,导致value无法被回收,出现内存泄漏。
线程生命周期过长如果线程生命周期过长,那么这些线程持有的ThreadLocalMap也不会被回收,即使ThreadLocal已经被回收,但ThreadLocalMap还在,value也无法被回收。这种情况下可能需要手动调用remove()方法清除数据。
解决内存泄露方法
每次使用完ThreadLocal,都调用它的remove()方法,清除数据值。
使用完线程后,确保线程销毁,不要使线程生命周期过长。
不需要维护的ThreadLocal及时设置为null,让它可被回收。
用try-finally块包装线程执行体,在finally块中清除ThreadLocal。
示例
public class ThreadLocalExample {
// 声明ThreadLocal变量
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 在主线程中设置threadLocal的值
threadLocal.set("Main Thread");
// 在子线程中获取和设置threadLocal的值
new Thread(() -> {
System.out.println(threadLocal.get()); // 输出null
threadLocal.set("Child Thread");
System.out.println(threadLocal.get()); // 输出Child Thread
}).start();
// 在主线程中再次打印threadLocal的值
System.out.println(threadLocal.get()); // 输出Main Thread
}
}
评论区