十五、线程池(六)自动创建线程池的弊端

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了十五、线程池(六)自动创建线程池的弊端相关的知识,希望对你有一定的参考价值。

参考技术A 所谓的自动创建线程池就是直接调用 Executors 的各种方法来生成常见的线程池,例如 Executors.newCachedThreadPool()。但这样做是有一定风险的,接下来就来逐一分析自动创建线程池可能带来哪些问题。

首先来看第一种线程池 FixedThreadPool, 它是线程数量固定的线程池,如源码所示,newFixedThreadPool 内部实际还是调用了 ThreadPoolExecutor 构造函数。

通过往构造函数中传参,创建了一个核心线程数和最大线程数相等的线程池,它们的数量也就是传入的参数,这里的重点是使用的队列是容量没有上限的 LinkedBlockingQueue,如果对任务的处理速度比较慢,那么随着请求的增多,队列中堆积的任务也会越来越多,最终大量堆积的任务会占用大量内存,并发生 OOM ,也就是OutOfMemoryError,这几乎会影响到整个程序,会造成很严重的后果。

第二种线程池是 SingleThreadExecutor,来分析下创建它的源码。

可以看出,newSingleThreadExecutor 和 newFixedThreadPool 的原理是一样的,只不过把核心线程数和最大线程数都直接设置成了 1,但是任务队列仍是无界的 LinkedBlockingQueue,所以也会导致同样的问题,也就是当任务堆积时,可能会占用大量的内存并导致 OOM。

第三种线程池是 CachedThreadPool,创建它的源码下所示。

这里的 CachedThreadPool 和前面两种线程池不一样的地方在于任务队列使用的是 SynchronousQueue,SynchronousQueue 本身并不存储任务,而是对任务直接进行转发,这本身是没有问题的,但会发现构造函数的第二个参数被设置成了 Integer.MAX_VALUE,这个参数的含义是最大线程数,所以由于 CachedThreadPool 并不限制线程的数量,当任务数量特别多的时候,就可能会导致创建非常多的线程,最终超过了操作系统的上限而无法创建新线程,或者导致内存不足。

第四种线程池 ScheduledThreadPool 和第五种线程池 SingleThreadScheduledExecutor 的原理是一样的,创建 ScheduledThreadPool 的源码如下所示。

而这里的 ScheduledThreadPoolExecutor 是 ThreadPoolExecutor 的子类,调用的它的构造方法如下所示。

通过源码可以看出,它采用的任务队列是 DelayedWorkQueue,这是一个延迟队列,同时也是一个无界队列,所以和 LinkedBlockingQueue 一样,如果队列中存放过的任务,就可能导致 OOM。

这几种自动创建的线程池都存在风险,相比较而言,自己手动创建会更好,因为我们可以更加明确线程池的运行规则,不仅可以选择适合自己的线程数量,更可以在必要的时候拒绝新任务的提交,避免资源耗尽的风险。

[多线程] 线程池的使用

避免使用 Executors 来创建线程。
说明:
Executors各个方法的弊端: 
1)newFixedThreadPool和newSingleThreadExecutor:    主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。 
2)newCachedThreadPool和newScheduledThreadPool:   主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
 
正例1: 
// 定时执行线程池
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());    
 
正例2:
//通用线程池
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); 
ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue (1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());  
pool.execute(()-> System.out.println(Thread.currentThread().getName())); 
pool.shutdown();  //gracefully shutdown    
 
正例3:   
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">       
// 请引入一个已经设置过工厂名称的线程工厂对象           
//in code userThreadPool.execute(thread);  
 
 

以上是关于十五、线程池(六)自动创建线程池的弊端的主要内容,如果未能解决你的问题,请参考以下文章

多线程(六):线程池

多线程系列六:线程池

多线程编程学习五(线程池的创建)

池化技术——自定义线程池

JAVA线程池的使用

阿里为何不允许用Executors创建线程池?