/**
* ThreadLocal
* 用完删除,防止线程复用,导致内存泄漏
*/
public class ThreadLocalTest02 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);//创建线程池
try {
Data data = new Data();
for (int i = 0; i <6 ; i++) {
threadPool.submit(()->{
try {
Integer b = data.threadLocal.get();
data.add();
Integer a = data.threadLocal.get();
System.out.println(Thread.currentThread().getName()+"\t"+b+"\t"+a);
} finally {
data.threadLocal.remove();//用完删除,防止线程复用,导致内存泄漏
}
});
}
} finally {
threadPool.shutdown();//关闭线程池
}
}
}
class Data{
int data=0;
//初始化值,匿名内部类写法
// ThreadLocal threadLocal=new ThreadLocal(){
// @Override
// protected Integer initialValue() {
// return 0;
// }
// };
//初始化值,推荐写法
ThreadLocal<Integer> threadLocal=ThreadLocal.withInitial(()->0);
public void add(){
threadLocal.set(threadLocal.get()+1);
}
}
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
【强制】必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如正好用在线程池),这些key为null的Entry的value就会一直存在一条强引用链。
虽然弱引用,保证了key指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现key为null时才会去回收整个entry、value,因此弱引用不能100%保证内存不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。
set,get,remove方法中,在threadLocal的生命周期里,针对threadLocal存在的内存泄漏的问题, 都会通过expungeStaleEntry,cleanSomeSlots,replaceStaleEntry这三个方法清理掉key为null的脏entry。