为什么推荐使用ThreadPoolExecutor来创建线程池

首先了解下Executors的方法创建出来的5种常用线程池

名称 说明
newCachedThreadPool 可缓存的线程池
newFixedThreadPool 固定大小的线程池
newSingleThreadExecutor 固定单个线程的线程池
newScheduledThreadPool 用作任务调度的线程池
newWorkStealingPool 足够大小的线程池,JDK1.8新增的

看下每种线程池都是怎么创建的

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

可以看到它允许创建的最大线程数是Integer.MAX_VALUE,使用的任务队列是SynchronousQueue,是个用于线程同步的阻塞队列,它没有实际的容量。所以如果直接创建这个线程池,很可能会创建大量线程,导致OOM。

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

可以看到它的核心线程数和最大线程数是一样的,由使用者来决定具体值,使用的任务队列是LinkedBlockingQueue,是个有界阻塞队列,如下源码,可知这个队列的最大长度是Integer.MAX_VALUE,这样可能会导致大量的请求堆积,直接OOM。

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

可以看到它的核心线程数和最大线程数是一样的,固定是1,使用的任务队列是LinkedBlockingQueue,是个有界阻塞队列,这个队列的最大长度是Integer.MAX_VALUE,这样可能会导致大量的请求堆积在这个队列中,导致出现OOM。

  • newScheduledThreadPool
   public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

可以看到它允许创建的最大线程数是Integer.MAX_VALUE,使用的队列是DelayedWorkQueue,是个支持延迟操作的无界阻塞队列。所以使用这个线程池,如果有大量线程被创建,就会导致OOM。

  • newWorkStealingPool
public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
    
public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode) {
        this(checkParallelism(parallelism),
             checkFactory(factory),
             handler,
             asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
             "ForkJoinPool-" + nextPoolId() + "-worker-");
        checkPermission();
    }

这个线程池它的实现和上面4种都不一样,用的是ForkJoinPool类。这是个并行的线程池,传入的参数是并发线程数,它里面的任务执行是无序的,哪个线程抢到任务就哪个线程执行,ForkJoinPool主要用到的是双端队列。

总结

从以上对各个类型的线程池的分析可以发现,除了newWorkStealingPool用的是ForkJoinPool类,其他线程池的构建最后其实都调用了ThreadPoolExecutor类的构造方法去创建线程池。而这些类型的线程池只是根据自己的需要来传入了一些默认的参数,也正是因为这些参数,才会导致可能出现OOM的问题。那么看下使用ThreadPoolExecutor来创建线程池的示例代码:

ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(1));

可以看到使用ThreadPoolExecutor来创建线程池,里面所有的参数我们都可以根据需求来自己指定,这样使用起来就放心了许多。所以推荐使用ThreadPoolExecutor来创建线程池。

发布了178 篇原创文章 · 获赞 180 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_38106322/article/details/104273474