为什么不建议通过Executors构建线程池

打印 上一主题 下一主题

主题 546|帖子 546|积分 1638

Executors类看起来功能照旧比较强大的,又用到了工厂模式、又有比较强的扩展性,重要的是用起来还比较方便,如:
  1. ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;
复制代码
即可创建一个固定巨细的线程池。
但是为什么在阿里巴巴Java开发手册中也明确指出,不允许使用Executors创建线程池呢

Executors存在什么标题

在阿里巴巴Java开发手册中提到,使用Executors创建线程池大概会导致OOM(OutOfMemory ,内存溢出),但是并没有说明为什么,那么接下来我们就来看一下到底为什么不允许使用Executors?
我们先来一个简单的例子,模拟一下使用Executors导致OOM的情况。
  1. /**
  2. * @author Hollis
  3. */
  4. public class ExecutorsDemo {
  5.     private static ExecutorService executor = Executors.newFixedThreadPool(15);
  6.     public static void main(String[] args) {
  7.         for (int i = 0; i < Integer.MAX_VALUE; i++) {
  8.             executor.execute(new SubThread());
  9.         }
  10.     }
  11. }
  12. class SubThread implements Runnable {
  13.     @Override
  14.     public void run() {
  15.         try {
  16.             Thread.sleep(10000);
  17.         } catch (InterruptedException e) {
  18.             //do nothing
  19.         }
  20.     }
  21. }
复制代码
通过指定JVM参数:-Xmx8m -Xms8m 运行以上代码,会抛出OOM:
  1. Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
  2.     at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
  3.     at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
  4.     at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
复制代码
以上代码指出,ExecutorsDemo.java的第16行,就是代码中的executor.execute(new SubThread());。
Executors为什么存在缺陷

通过上面的例子,我们知道了Executors创建的线程池存在OOM的风险,那么到底是什么原因导致的呢?我们必要深入Executors的源码来分析一下。
实在,在上面的报错信息中,我们是可以看出蛛丝马迹的,在以上的代码中实在已经说了,真正的导致OOM的实在是LinkedBlockingQueue.offer方法。
  1. Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
  2.     at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
  3.     at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
  4.     at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
复制代码
如果读者翻看代码的话,也可以发现,实在底层确实是通过LinkedBlockingQueue实现的:
  1. public static ExecutorService newFixedThreadPool(int nThreads) {
  2.         return new ThreadPoolExecutor(nThreads, nThreads,
  3.                                       0L, TimeUnit.MILLISECONDS,
  4.                                       new LinkedBlockingQueue<Runnable>());
复制代码
如果读者对Java中的阻塞队列有所了解的话,看到这里大概就能够明白原因了。
Java中的BlockingQueue主要有两种实现,分别是ArrayBlockingQueue 和 LinkedBlockingQueue。
ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。
LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无界限的阻塞队列,最大长度为Integer.MAX_VALUE。
这里的标题就出在:不设置的话,将是一个无界限的阻塞队列,最大长度为Integer.MAX_VALUE。也就是说,如果我们不设置LinkedBlockingQueue的容量的话,其默认容量将会是Integer.MAX_VALUE。
而newFixedThreadPool中创建LinkedBlockingQueue时,并未指定容量。此时,LinkedBlockingQueue就是一个无界限队列,对于一个无界限队列来说,是可以不绝的向队列中参加任务的,这种情况下就有大概因为任务过多而导致内存溢出标题。
上面提到的标题主要体现在newFixedThreadPool和newSingleThreadExecutor两个工厂方法上,并不是说newCachedThreadPool和newScheduledThreadPool这两个方法就安全了,这两种方式创建的最大线程数大概是Integer.MAX_VALUE,而创建这么多线程,必然就有大概导致OOM。
扩展知识

如何正确创建线程池

制止使用Executors创建线程池,主要是制止使用此中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。
  1. private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
  2.         60L, TimeUnit.SECONDS,
  3.         new ArrayBlockingQueue(10));
复制代码
这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛`java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有界限队列,队列已经满了便无法继承处理新的哀求。但是异常(Exception)总比发生错误(Error)要好。
除了自己定义ThreadPoolExecutor外。另有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。
作者推荐使用guava提供的ThreadFactoryBuilder来创建线程池。
  1. public class ExecutorsDemo {
  2.     private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
  3.         .setNameFormat("demo-pool-%d").build();
  4.     private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
  5.         0L, TimeUnit.MILLISECONDS,
  6.         new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
  7.     public static void main(String[] args) {
  8.         for (int i = 0; i < Integer.MAX_VALUE; i++) {
  9.             pool.execute(new SubThread());
  10.         }
  11.     }
  12. }
复制代码
通过上述方式创建线程时,不但可以制止OOM的标题,还可以自定义线程名称,更加方便的堕落的时候溯源

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

李优秀

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表