当前位置: 首页 > 科技观察

阿里二面:ThreadLocal为什么会内存泄露

时间:2023-03-22 17:04:01 科技观察

阿里二面:ThreadLocal为什么会泄漏内存?.当使用场景需要为不同的线程保存不同的信息时。基本使用publicclassTestThreadLocal{privatestaticThreadLocalthreadLocal=newThreadLocal();//privatestaticThreadLocalthreadLocal=newThreadLocal(){////@Override//protectedIntegerinitialValue(){//返回0;//}//};publicstaticvoidmain(String[]args){Threadt1=newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println(threadLocal.get());}});Threadt2=newThread(newRunnable(){@Overridepublicvoidrun(){threadLocal.set(3);System.out.println("t2:"+threadLocal.get());}});t1.开始();t2.开始();System.out.println(threadLocal.get());}}如果需要设置默认值,可以实现initialValue方法。典型场景一:我们知道如果SimpleDateFormat的对象被多个线程使用,就会出现线程不安全的情况。具体代码如下:publicclassTestThreadLocal{publicstaticExecutorServiceexecutorService=Executors.newFixedThreadPool(16);privatestaticSimpleDateFormatsimpleDateFormat=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");publicstaticvoidmain(String[]args)throwsInterruptedException{for(inti=0;i<1000;i++){executorService.submit(newRunnable(){@Overridepublicvoidrun(){Stringformat=simpleDateFormat.format(newDate());try{Dateparse=simpleDateFormat.parse("2021-09-0100:00:00");}catch(ParseExceptione){e.printStackTrace();}System.out.println(格式);}});}Thread.sleep(3000);executorService.shutdownNow();}}运行结果如下:可以看出出现了异常。方法一:如果我们每次都能换成new一个新的SimpleDateFormat对象,再跑一遍就没问题了。但是有些资源被浪费了。方法二:使用ThreadLocal解决。假设线程池中一共有16个线程,那么我们一共有16个SimpleDateFormat对象来处理所有的日期格式化调用。代码如下:publicclassTestThreadLocal{publicstaticExecutorServiceexecutorService=Executors.newFixedThreadPool(16);privatestaticThreadLocalthreadLocal=newThreadLocal(){@OverrideprotectedSimpleDateFormatinitialValue(){returnnewSimpleDateFormat("yyyy-MM-ddHH:mm:ss");}};privatestaticSimpleDateFormatsimpleDateFormat=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");publicstaticvoidmain(String[]args)throwsInterruptedException{for(inti=0;i<1000;i++){executorService.submit(newRunnable(){@Overridepublicvoidrun(){Stringformat=threadLocal.get().format(newDate());try{Dateparse=threadLocal.get().parse("2021-09-0100:00:00");}catch(ParseExceptione){e.printStackTrace();}System.out.println(格式);}});}Thread.sleep(3000);executorService.shutdownNow();}}注意:如果不使用线程池,线程结束时线程中的threadLocalMap也会被回收如果使用线程池,线程池中的线程会被复用,线程中的threadLocalMap不会被回收,造成内存泄漏。按照正确的使用方法,remove应该每次都用完,但是这样效率很低。不如方法一每次都新建一个SimpleDateFormat对象。(不过个人觉得还好,漏了一点也无所谓,但是threadlocal毕竟不是专门为解决线程安全问题而设计的,所以不建议这样使用)正确的使用方法就是在每次使用ThreadLocal的时候调用它的remove()方法清除数据,将ThreadLocal变量定义为privatestatic,这样就会一直有ThreadLocal的强引用,也可以保证Entry的值可以随时通过ThreadLocal的弱引用访问,然后清零。为什么ThreadLocal的高级部分ThreadLocal会泄漏内存?内存泄漏是指程序中已经动态分配的堆内存由于某种原因没有被释放或不能被释放,造成系统内存的浪费,导致程序运行速度变慢甚至严重的后果系统崩溃。试想:一个线程对应一块工作内存,一个线程可以存放多个ThreadLocal。那么假设启动了10000个线程,每个线程创建了10000个ThreadLocal,也就是每个线程在一个小内存空间中维护了10000个ThreadLocal,当线程执行结束的时候,假设这些ThreadLocal中的Entry不会被回收,那么很容易导致堆内存溢出。该怎么办?JVM不提供任何解决方案吗?答:JVM使用ThreadLocalMap的Key作为弱引用来避免内存泄漏。当JVM调用remove、get和set方法时,它会回收脏值。ThreadLocal的关系图如下:Thread维护了一个ThreadLocalMap,这个map中的key是一个弱引用的readLocal实例。value就是我们设置进去的值。当threadLocal实例对象设置为null时,没有强引用指向threadLocal实例,所以headLocal会被gc回收。但是我们的值不会被回收,因为有线程连接的强引用。只有线程结束,强引用断开,map,value等才会全部回收。如下图所示:但是很多时候我们会用到线程池。为了复用线程,线程的生命周期还没有结束,所以不能回收,导致内存泄漏。

猜你喜欢