目录
一、什么是ThreadLocal?
ThreadLocal翻译过来就是【线程本地 / 线程本地化 / 本地线程变量】,也就是说,ThreadLocal中填充的是当前线程的变量,该变量对其他线程而言,是封闭且隔离的,ThreadLocal为变量再每个线程中创建一个副本,这样每个线程都可以访问自己内部的副本变量。ThreadLocal是线程本地存储,再每个线程中都创建了一个ThreadLocalMap对象,每个线程可以访问自己内部ThreadLocalMap对象内的value。
二、ThreadLocal如何使用?
以下为示例:
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread();
thread.start();
threadLocal.set(thread.getName());
System.out.println("线程:" + thread.getName() + ",Local:" + threadLocal.get() + ":" + i);
threadLocal.remove();
}
}
输出结果:
线程:Thread-0,Local:Thread-0:0
线程:Thread-1,Local:Thread-1:1
线程:Thread-2,Local:Thread-2:2
线程:Thread-3,Local:Thread-3:3
线程:Thread-4,Local:Thread-4:4
线程:Thread-5,Local:Thread-5:5
线程:Thread-6,Local:Thread-6:6
线程:Thread-7,Local:Thread-7:7
线程:Thread-8,Local:Thread-8:8
线程:Thread-9,Local:Thread-9:9
代码解析:
1、这段代码创建了一个ThreadLocal对象,并在一个for循环中启动了10个线程。
2、ThreadLocal是一个Java线程范围内的变量。
3、每个线程都拥有自己独立的ThreadLocal变量副本,可以在不同的线程中存储不同的值。
4、在这段代码中,每个线程都通过调用threadLocal.set()方法将当前线程的名称存储在ThreadLocal对象中。
5、然后通过调用threadLocal.get()方法来获取ThreadLocal中存储的值,并将其和线程名称和循环变量i一起打印出来。
6、最后,调用threadLocal.remove()方法来清除ThreadLocal中的值,以确保下一个线程使用的是一个干净的ThreadLocal对象,防止内存溢出问题。
从结果中,我们可以看出,每一个线程都有自己的Local值,这就是ThreadLocal的基础使用方法。
三、ThreadLocal内存泄漏问题
1、什么是内存泄漏
广义通俗的来说,内存泄漏就是:不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄漏。
2、强引用与弱引用
强引用,使用最普遍的引用,一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError异常 / 错误,使程序异常终止,也不回收这种对象。如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。
弱引用,JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。
3、GC回收机制
垃圾回收机制,JVM找到需要回收的对象有以下两种方式:
- 引用计数法:每个对象有一个引用技术属性,新增一个引用时计数加1,引用释放时,计数减1,计数为0时可以回收;
- 可达性分析法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
4、ThreadLocal内存泄漏问题
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。这些对象之间的引用关系如下:
从上图中可以看出,hreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,key(ThreaddLocal)势必会被GC回收,这样就回导致ThreadLocalMap 中Key为null,而value还存在着强引用,只有Thread线程退出以后,value的强引用链条才会断掉。但是如果,当前线程在迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value,永远无法回收,造成内存泄漏。由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。但是使用弱引用可以多一层保障,弱引用ThreadLocadl不会内存泄漏,对应的value在下一次ThreadLocalMap调用set 、get、remove的时候会被清除。因此,导致内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除过key就会导致内存泄漏,而不是因为弱引用。
5、ThreadLocal正确的使用方法
- 每次使用完ThreadLocal都调用remove()方法清除数据
- 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。