构造方法参数说明

  • corePoolSize 核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。
  • maximumPoolSize 线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
  • keepAliveTime 非核心线程的闲置超时时间,超过这个时间就会被回收。
  • unit 指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。
  • workQueue 线程池中的任务队列. 常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。
  • threadFactory
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("name-pool-%d").setDaemon(true).build();

可以按照此方法定制threadFactory

  • RejectedExecutionHandler

    当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution方法。

new ThreadPoolExecutor.AbortPolicy()

线程池规则

线程池的线程执行规则跟任务队列有很大的关系。 当任务队列没有大小限制时:

  • 如果线程数量<=核心线程数量,那么直接启动一个核心线程来执行任务,不会放入队列中。
  • 如果线程数量>核心线程数,但<=最大线程数,并且任务队列是LinkedBlockingDeque的时候,超过核心线程数量的任务会放在任务队列中排队。
  • 如果线程数量>核心线程数,但<=最大线程数,并且任务队列是SynchronousQueue的时候,线程池会创建新线程执行任务,这些任务也不会被放在任务队列中。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。
  • 如果线程数量>核心线程数,并且>最大线程数,当任务队列是LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队。也就是当任务队列是LinkedBlockingDeque并且没有大小限制时,线程池的最大线程数设置是无效的,他的线程数最多不会超过核心线程数。
  • 如果线程数量>核心线程数,并且>最大线程数,当任务队列是SynchronousQueue的时候,会执行线程池的拒绝策略。

当任务队列大小有限制时:

  • 当LinkedBlockingDeque塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会执行线程池的拒绝策略。
  • SynchronousQueue没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会执行线程池的拒绝策略。

LinkedBlockingDeque:

ExecutorService executor=new ThreadPoolExecutor(8,16,0, TimeUnit.DAYS,new LinkedBlockingDeque<>(20),threadFactory,new ThreadPoolExecutor.AbortPolicy());

当线程数为8时,全部交由核心线程完成,当线程超过8时,放到队列里等待创建线程,当超过队列大小时,也就是有28个线程时,将会直接创建线程,当总线程数超过16时,也就是当等待队列和执行中线上加起来超过36(20+16)时,将会执行线程池的拒绝策略。

SynchronousQueue:

ExecutorService executor=new ThreadPoolExecutor(8,16,0, TimeUnit.DAYS,new SynchronousQueue<>(),threadFactory,new ThreadPoolExecutor.AbortPolicy());

当线程数为8时,全部交由核心线程完成,当线程超过8时,将会直接创建线程,当总线程数超过16时,将会执行线程池的拒绝策略。

个人推荐的线程池构造

一、用LinkedBlockingDeque,CallerRunsPolicy

  • 一般建议用CallerRunsPolicy,我们都不希望把任务丢失,但是如果实在是微不足道的任务,也可以抛出异常,但是要做好异常捕捉后的工作。
  • LinkedBlockingDeque的好处就是可以有一个队列可以缓冲,不至于全部以最大线程数去执行。
  • 下面这个线程池用于CPU密集型的,如果是IO密集型的话,可以把核心线程改成16,甚至更大(根据实际需要),最大线程数改成32,也不建议太大,那样会占用太多的线程数。
    private static final ExecutorService pool = new ThreadPoolExecutor(8, 16, 0, TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(1024),
            new ThreadFactoryBuilder().setNameFormat("message-pool-%s").build(),
            new ThreadPoolExecutor.CallerRunsPolicy());

二、用SynchronousQueue,CallerRunsPolicy SynchronousQueue的好处在于不占用队列空间,直接由线程池运行,缺点在于少了缓冲的空间。下面这个还是CPU密集型的,如果是IO密集型参考上面。

    private static final ExecutorService pool = new ThreadPoolExecutor(8, 16, 0, TimeUnit.SECONDS,
            new SynchronousQueue<>(),
            new ThreadFactoryBuilder().setNameFormat("message-pool-%s").build(),
            new ThreadPoolExecutor.CallerRunsPolicy());