Java语言虽然内置了多线程支持,启动一个新线程非常方便,但是,创建线程需要操作系统资源(线程资源,栈空间等),频繁创建和销毁大量线程需要消耗大量时间
如果可以复用一组线程:
在没有任务时线程处于空闲状态,当请求到来:线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不是销毁)。这样就实现了线程的重用。
为每个请求都开一个新的线程虽然理论上是可以的,但是会有缺点:
所以说:我们的线程最好是交由线程池来管理,这样可以减少对线程生命周期的管理,一定程度上提高性能。
前面已经整理了两个常见的创建方式:继承 Thread,重写Run() 方法 和 实现 Runnable 接口重写 run() 方法。
从 JDK1.5 开始,Java 提供 Callable 接口,提供了另一种方式:实现 Callable 接口创建线程,这个方式可以获取到线程执行的返回值、以及是否执行完成等信息。
在简单应用中前两种创建方式可以满足,但是在业务量大时,随着线程数量的增多,反而会耗尽 CPU 和内存资源,如果处理不当就会 OOM。因此就很有必要引入线程池
在线程频繁调度的场景中,JDK1.5 以前,必须手动打造线程池,来节约系统开销;而从 JDK1.5 开始,Java 提供了一个 Excutors 工厂类来生产线程池,进行线程控制
Executors 目前提供的 5 种不同的线程池创建配置:
newCachedThreadPool() 一种用来处理大量短时间工作任务的线程池
newFixedThreadPool(int nThreads) 重用指定数目(nThreads)的线程
newSingleThreadExecutor() 线程池中的线程数量为 1,操作一个无界的工作队列
newSingleThreadScheduledExecutor() 创建只有一条线程的线程池
newWorkStealingPool(int parallelism) Java 8 才加入这个创建方法
newScheduledThreadPool(int corePoolSize) 创建具有指定线程数的线程池
void execute(Runnable command)
Executor 的设计是源于 Java 早期线程 API 使用的教训,开发者在实现应用逻辑时,被太多线程创建、调度等不相关细节所打扰。就像我们进行 HTTP 通信,如果还需要自己操作 TCP 握手,开发效率低下,质量也难以保证
从源码上看,无论是 newFixedThreadPool() 方法、newSingleThreadExecutor() 方法,还是 newCachedThreadPool() 方法,其背后均使用了 ThreadPoolExecutor
private final BlockingQueue<Runnable> workQueue
private final HashSet<Worker> workers = new HashSet<>()
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
构造函数的参数释义
有关 workQueue 以及 handler 参数
参数 BlockingQueue workQueue,是用于存放提交尚未被执行的任务的队列,类型是 BlockingQueue 接口的对象,用于存放 Runnable 对象
参数 RejectedExecutionHandler handler 是指当任务数量超过系统承载能力时,该如何处理?其中 JDK 提供了四种拒绝策略
通过配置不同的参数,我们就可以创建出行为大相径庭的线程池,这就是线程池高度灵活性的基础
线程池虽然为提供了非常强大、方便的功能,使用不当同样会导致问题:
线程数 = CPU 核数 × 目标 CPU 利用率 ×(1 + 平均等待时间 / 平均工作时间)