当前位置: 首页 > 后端技术 > Java

访谈攻略32:为什么一定要用ThreadPoolExecutor来创建线程池?

时间:2023-04-01 20:52:13 Java

在Java语言中,并发编程依赖于线程池,创建线程池的方式有很多种。但是从大类上来说,线程池的创建可以分为两类:手动使用ThreadPoolExecutor创建线程池和使用Executors自动创建线程池。那么使用哪种方式来创建线程池呢?今天就来详细说说。先说结论吧。在Java语言中,必须使用ThreadPoolExecutor手动创建线程池,因为这种方式可以通过参数控制最大任务数和拒绝策略,使得线程池的执行更加透明可控,可以避免资源风险筋疲力尽。OOM风险论证如果我们使用Executors自动创建线程池来创建线程池,那么就会存在线程溢出的风险。以CachedThreadPool为例进行演示:importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;publicclassThreadPoolExecutorExample{staticclassOOMClass{//创建1MB变量(1M=1024KB=1024*1024Byte)privatebyte[]data_byte=新字节[1*1024*1024];}publicstaticvoidmain(String[]args)throwsInterruptedException{//使用executor自动创建线程池ExecutorServicethreadPool=Executors.newCachedThreadPool();Listlist=newArrayList<>();//添加任务for(inti=0;i<10;i++){intfinalI=i;threadPool.execute(newRunnable(){@Overridepublicvoidrun(){//定时添加try{Thread.sleep(finalI*200);}catch(InterruptedExceptione){e.printStackTrace();}//向集合中添加1M个对象OOMClassoomClass=newOOMClass();list.add(oomClass);System.out.println("执行任务:"+finalI);}});第二步,在Idea中设置JVM的最大运行内存为10M(这个值主要是为了方便演示),如下图所示:上面程序的执行结果如下图所示下图:从上面的结果可以看出,线程执行到7次后,会出现OutOfMemoryError内存溢出异常。内存溢出原因分析要了解内存溢出的原因,我们需要查看CachedThreadPool的实现细节。它的源码如下图所示:构造函数的第一部分两个参数设置为Integer.MAX_VALUE,表示最大线程数,所以由于CachedThreadPool不限制线程数,当任务数特别大,会创建很多线程。在上面的OOM例子中,每个线程至少要消耗1M内存,JDK系统类的加载也会占用一部分内存,所以当总运行内存大于10M时,就会出现内存溢出。使用ThreadPoolExecutor改善接下来我们使用ThreadPoolExecutor改善OOM问题。我们使用ThreadPoolExecutor手动创建线程池,创建最大线程数为2的线程池,最多存放2个任务,并设置线程池的拒绝策略为忽略新任务,使得运行线程池的内存大小不会超过10M。实现代码如下:importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.*;/***ThreadPoolExecutordemoexample*/publicclassThreadPoolExecutorExample{staticclassOOMClass{//创建一个1MB的变量(1M=1024KB=1024*1024Byte)privatebyte[]data_byte=newbyte[1*1024*1024];}publicstaticvoidmain(String[]args)throwsInterruptedException{//手动创建线程池,最大线程数为2,最多可存放2个任务,其他任务将被忽略ThreadPoolExecutorthreadPool=newThreadPoolExecutor(2,2,0L,TimeUnit.SECONDS,newLinkedBlockingQueue<>(2),newThreadPoolExecutor.DiscardPolicy());//拒绝策略:忽略任务Listlist=newArrayList<>();//添加任务for(inti=0;i<10;i++){intfinalI=i;日readPool.execute(newRunnable(){@Overridepublicvoidrun(){//添加定期尝试{Thread.sleep(finalI*200);}catch(InterruptedExceptione){e.printStackTrace();}//设置1m对象被添加到集合OOMClassoomClass=newOOMClass();list.add(oomClass);System.out.println("Executetask:"+finalI);}});}//关闭线程池threadPool.shutdown();//检测到线程池中的任务正在执行while(!threadPool.awaitTermination(3,TimeUnit.SECONDS)){System.out.println("线程池中还有任务正在处理中");}}}上述程序的执行结果如下图所示:从上面的结果可以看出,线程池从开始到执行结束都没有出现OOM异常,这是手动的优势创建线程池创建线程池的其他问题除了CachedThreadPool线程池之外,其他使用Executors自动创建线程池的方法也存在其他问题。例如FixedThreadPool的源码如下:默认情况下,任务队列LinkedBlockingQueue的存储容量为Integer。MAX_VALUE也趋于无穷大,如下图所示:这样也会造成线程池中任务过多导致的内存溢出问题。其他几种使用Executors自动创建线程池的方式也存在这个问题,这里就不一一演示了。总结线程池的创建方式分为两类:手动使用ThreadPoolExecutor创建线程池和自动使用Executors创建线程池。其中,使用Executors自动创建线程,由于线程或任务的数量不可控,可能会导致内存溢出的风险,所以在创建线程池时,建议使用ThreadPoolExecutor来创建。判断是非在自己,名誉在别人,得失在人数。公众号:Java面试真题分析面试合集:https://gitee.com/mydb/interview