常用线程池(4种)

核心参数

  • int corePoolSize:线程池中 核心线程数最大值。
  • int maximumPoolSize:线程池中 线程总数最大值。
  • long keepAliveTime:非核心线程闲置超时时间。
  • TimeUnit unit:keepAliveTime的时间单位。
  • BlockingQueue workQueue(非必须):阻塞队列,维护着等待执行的Runnable任务对象。
    • LinkedBlockingQueue:链式阻塞队列,底层数据结构是链表,默认大小Integer.MAX_VALUE,也可以指定队列大小。
    • ArrayBlockingQueue:数组阻塞队列,底层数据结构是数组,需要指定队列的大小。
    • SynchronousQueue:同步队列,内部容量为0,每个put操作必须等待一个take操作,反之亦然。
    • DelayQueue:延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能从队列中获取到该元素。
  • ThreadFactory threadFactory (非必须):创建线程的工厂,用于批量创建线程,统一在创建线程的时候设置一些参数(如:是不是守护线程,线程的优先级),不指定,创建默认的线程工厂。
  • RejectedExecutionHandler handler:拒绝处理策略,线程数量 > 最大线程数量时,就会采用拒绝处理策略。
    • ThreadPoolExecutor.AbortPolicy:默认拒绝策略,丢弃任务并抛出RejectedExecutionException异常。
    • ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,不抛异常。
    • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列头部(最旧的)的任务,然后重新尝试执行程序,如果再次失败,重复此过程。
    • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

newCachedThreadPool

可缓存线程池;

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

运行流程:

  1. 提交任务进线程池。
  2. corePoolSize为0,不创建核心线程,线程池最大为Integer.MAX_VALUE。
  3. 尝试将任务添加到SynchronousQueueduilie。
  4. 如果SynchronousQueue入列成功,等待被当前运行的线程空闲后拉取执行;如果当前没有空闲线程,那么就创建一个非核心线程,然后从SynchronousQueue队列拉取任务并在当前线程执行。
  5. 如果SynchronousQueue队列已经有任务在等待,入列操作将被阻塞。

概述:

当需要执行很多短时间的任务,newCachedThreadPool线程池的线程复用率比较高,会显著的提升性能。而且线程60秒后会回收,意味着即使没有任务进来,newCachedThreadPool也不会占用很多资源。

缺点:

线程池中的线程数最大数是Integer.MAX_VALUE,可能会创建非常多的线程,几乎不会触发拒接策略,甚至造成OOM。

newFixedThreadPool

定长线程池;

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

特点:

  1. 因为corePoolsize 等于maximumPoolSize (核心线程数量和总线程数量相等),所以newFixedThreadPool只会创建核心线程。
  2. 在getTask()方法,如果队列里没有任务可取,线程会一直阻塞在LinkedBlockingQueue.take(),线程不会被回收。
  3. 由于线程不会被回收,会一直卡在阻塞,所以在没有任务的情况下,占用的资源多。
  4. 因为阻塞队列可以很大(最大值为Integer最大值),所以几乎不会触发拒绝策略。

概述:

核心线程数量和总线程数量相等,所以只能创建核心线程,不能创建非核心线程。因为LinkedBlockingQueue队列的默认大小是Integer.MAX_VALUE,所以如果核心线程空闲,则交给核心线程处理;如果核心线程不空闲,则入列等待,直到核心线程空闲。

缺点:

堆积的请求进入阻塞队列可能会消耗非常大的内存,甚至造成OOM。

newSingleThreadExecutor

单线程的线程池

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

特点:

  1. 有且仅有一个核心线程corePoolsize = maximumPoolSize = 1。
  2. 使用了LinkedBlockingQueue队列(容量是Integer.MAX_VALUE)。
  3. 先来先执行的顺序执行

概述:

有且仅有一个核心线程corePoolsize = maximumPoolSize = 1。使用了LinkedBlockingQueue队列(容量是Integer.MAX_VALUE),容量很大,不会创建非核心线程,并且任务是按照先进来先执行的顺序执行的,当这个唯一线程非空闲的时候,新的任务存储在阻塞队列中。

缺点:

因为使用的LinkedBlockingQueue队列容量太大,不容易触发拒绝策略,容易造成OOM。

newScheduledThreadPool

定长线程池,支持定时以及周期性

  1. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize
  2. return new ScheduledThreadPoolExecutor(corePoolSize);
  3. }
  4. //ScheduledThreadPoolExecutor():
  5. public ScheduledThreadPoolExecutor(int corePoolSize) {
  6. super(corePoolSize, Integer.MAX_VALUE,
  7. DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
  8. new DelayedWorkQueue());
  9. }

为什么要使用线程池?

  1. 控制并发数量
  2. 可以复用已经创建的线程(主要原因)
    1. 主要还是因为为了性能,线程的创建涉及cpu状态的切换,如果线程能被复用会大大降低这种状态切换带来的性能损耗
  3. 对线程统一管理