线程池

线程池带来的好处:

  1. 降低资源消耗
    通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度
    当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性
    线程是稀缺资源,使用线程池可以统一分配、调优和监控。

实现原理

当提交一个新任务到线程池时,线程池的主要处理流程

  1. 判断核心线程池里的线程是否都在执行任务。
    如果不是,则创建一个新的工作线程来执行任务;
    如果核心线程池的线程都在执行任务,则进入下个流程。
  2. 判断工作队列是否已满。
    如果没有,则将新提交的任务存储在这个工作队列;
    如果已满,则进入下个流程。
  3. 判断线程池的线程是否都处于工作状态。
    如果不是,则创建一个新的工作线程来执行任务;
    如果是的,则交给饱和策略来处理这个任务。

ThreadPoolExecutor执行execute()方法的示意图

ThreadPoolExecutor执行execute()分4种情况:

  1. 如果当前运行的线程少于corePoolSize,则创建新的线程来执行任务。(该步骤需要获取全局锁)
  2. 如果运行的线程等于或多于corePoolSize,则将任务加入到BlockingQueue
  3. 如果无法将任务加入到BlockingQueue(队列已满),则创建新的线程来执行任务。(该步骤需要获取全局锁)
  4. 如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandlerrejectedExecution()方法。

线程池的使用

创建

1
2
3
4
5
6
7
8
9
10
/**
* ThreadPoolExecutor构造方法
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){...}

不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:

1
2
3
Executors.newCachedThreadPool(); //创建一个线程池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor(); //创建容量为1的线程池
Executors.newFixedThreadPool(int); //创建固定容量大小的线程池

corePoolSize(核心线程池的大小)

corePoolSize,线程池的基本大小。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于corePoolSize就不会再创建。

调用prestartAllCoreThreads()线程池会提前创建并启动所有基本线程。

workQueue(任务队列)

workQueue任务队列:用于保存等待执行的阻塞队列。
有以下几种选择:

  1. ArrayBlockingQueue
    ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列,FIFO。
  2. LinkedBlockingQueue
    LinkedBlockingQueue是单向链表实现的阻塞队列,FIFO。
  3. SynchronousQueue
    SynchronousQueue是一个不存元素的阻塞队列。每一个插入操作必须等待另一个线程调用移除操作,否则一直处于阻塞状态。吞吐量高于LinkedBlockingQueueArrayBlockingQueue
  4. PriorityBlockingQueue
    PriorityBlockingQueue是一个具有优先级的无界阻塞队列。

maximumPoolSize(线程池最大数量)

maximumPoolSize是线程池允许创建的最大线程数。如果任务队列满了,并且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程执行任务。

如果使用了无界阻塞队列,则该参数就没有效果。

ThreadFactory

ThreadFactory用于设置创建线程的工厂,可以通过线程工厂来给每个创建出来的线程设置更有意义的名字。

Guava框架提供的ThreadFactoryBuilder可以快速给线程池里的线程设置有意义的名字。

RejectedExecutionHandler(饱和策略)

RejectedExecutionHandler饱和策略:当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。默认情况下是AbortPolicy
Java线程池框架提供了4种策略:

  • AbortPolicy:直接抛出异常。
  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardPolicy:不处理,直接丢弃掉。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

keepAliveTime(线程活动保持时间)

线程池的工作线程空闲后,保持存活的时间。
任务多且任务执行时间较短,可以将该参数设置大点,提高线程的存活率。

TimeUnit(线程活动保持时间的单位)

可选单位有:DAYS、HOURS、MINUTES、MILLISECONDS、MICROSECONDS、NANOSECONDS。

提交任务

可以使用两个方法向线程池提交任务:execute()submit()

execute()

execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。

submit()

submit()方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过该对象可以判断任务是否执行成功,并通过Future的get()方法获取返回值,get()方法会阻塞当前线程直到任务完成,get(long timeout, TimeUnit unit)方法会阻塞当前线程一段时间后立即返回,这时任务肯能没有执行完。

submit()方法实际上将任务构造成了FutureTask然后调用execute()方法执行。

关闭线程池

可以调用线程池的shutdown()或者shutdownNow()方法来关闭线程池。
原理:遍历线程池中的工作线程,然后逐个调用线程的interrupt()来中断线程,所以无法响应中断的任务可能无法终止