为什么不推荐使用Executors创建线程池?(面试常问)

一、FixedThreadPool

当我们使用Executors创建FixedThreadPool时,其对应的构造方法为:

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

此方式创建的线程池具有固定的核心线程数,究其源码可以发现它返回的ThreadPoolExcutor中传入的队列使用的是无界阻塞队列,这就会导致如果使用该线程池执行任务,当任务数超过所设定的核心线程数时,就会不断的将任务添加到队列中,任务越多占用的内存就会越多,最终可能耗尽内存,导致JVM没有内存可以用,这就会出现OOM(内存溢出)。

二、CachedThreadPool

当我们使用Executors创建CachedThreadPool时,其对应的构造方法为:

  public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

此方式创建的线程池可以根据需用去动态的创建新的非核心线程,究其源码我们可以发现,此线程池没有核心线程,由于没有核心线程,当它收到的任务量非常大时,线程池可能会创建大量的线程,从而消耗大量的系统资源

三、SingleThreadExecutor

当我们使用Executors创建SingleThreadExecutor时,其对应的构造方法为:

  public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

此方式创建的线程池只有一个工作线程,适用于保证任务按照顺序执行的场景。我们可以发现它也和FixedThreadPool一样使用的是无界阻塞队列,并且此线程池只有一个线程,如果当此线程由于异常终止而结束,线程池就会创建一个新的线程来代替它,这就可能会导致无限制的创建新的线程,从而消耗系统的资源。

四.总结

  • FixedThreadPoolSingleThreadExecutor使用的队列类型是LinkedBlockingQueue,当任务量很大时,容易造成内存溢出。
  • CachedThreadPool当任务量非常大时,会创建大量的线程,从而消耗大量的系统资源
  • Executors创建的线程池,其底层原理都是使用了new ThreadPoolExecutor()的方式,根据不同的传参,实现不同的线程池使用范围
  • Executors创建的线程池被写死了,并且不能被灵活的调用,去解决特定的需求:例如需要限制线程池的大小或者使用特定类型的队列
  • Executors创建的线程池不能自定义线程的名字,不利于排查问题。

因此,为了更好地控制和管理线程池,推荐使用ThreadPoolExecutor类来手动创建线程池,它可以自定义线程名称,还可以根据具体的需求自己设置线程池的参数:例如核心线程数、最大线程数、队列类型、线程工厂等、以及自定义拒绝策略来处理任务无法执行的情况。

你可能感兴趣的:(线程,面试,java,eclipse)