程序中,我们会用各种池化技术来缓存创建昂贵的对象,比如线程池、连接池、内存池。一般是预先创建一些对象放入池中,使用的时候直接取出使用,用完归还以便复用,还会通过一定的策略调整池中缓存对象的数量,实现池的动态伸缩。
由于线程的创建比较昂贵,随意、没有控制地创建大量线程会造成性能问题,因此短平快的任务一般考虑使用线程池来处理,而不是直接创建线程。
今天,我们就针对线程池这个话题展开讨论,通过三个生产事故,来看看使用线程池应该注意些什么。
线程池的声明需要手动进行
Java中的Executors类定义了一些快捷的工具方法,来帮助我们快速创建线程池。《阿里巴巴Java开发手册》中提到,禁止使用这些方法来创建线程池,而应该手动newThreadPoolExecutor来创建线程池。这一条规则的背后,是大量血淋淋的生产事故,最典型的就是newFixedThreadPool和newCachedThreadPool,可能因为资源耗尽导致OOM问题。
首先,我们来看一下newFixedThreadPool为什么可能会出现OOM的问题。
我们写一段测试代码,来初始化一个单线程的FixedThreadPool,循环1亿次向线程池提交任务,每个任务都会创建一个比较大的字符串然后休眠一小时:
GetMapping(oom1)publicvoidoom1()throwsInterruptedException{ThreadPoolExecutorthreadPool=(ThreadPoolExecutor)Executors.newFixedThreadPool(1);//打印线程池的信息,稍后我会解释这段代码printStats(threadPool);for(inti=0;i;i++){threadPool.execute(()-{Stringpayload=IntStream.rangeClosed(1,).mapToObj(__-a).collect(Collectors.joining())+UUID.randomUUID().toString();try{TimeUnit.HOURS.sleep(1);}catch(InterruptedExceptione){}log.info(payload);});}threadPool.shutdown();threadPool.awaitTermination(1,TimeUnit.HOURS);}执行程序后不久,日志中就出现了如下OOM:
Exceptioninthread