1.为什么要用线程池
- 减少在创建和销毁线程上所花的时间以及系统资源的开销,从而提高系统的响应速度;
- 避免系统创建大量线程而导致消耗完系统内存,发生OutOfMemoryError;
2.线程池类图
3.线程池工作原理
4.如何创建线程池
方式一:通过ThreadPoolExecutor的构造方法实现
ThreadPoolExecutor提供了四个构造方法,具体参数作用在后面解析。
方式二:通过Executor 框架的工具类Executors来实现
Executors为我们提供了四种类型的线程池,但是《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
使用Executors创建线程池的弊端:
- FixedThreadPool 和 SingleThreadExecutor :允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致OOM。
/**
* 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
* 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
* 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果
* 这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。
* 此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
/*上面两个传入的阻塞队列长度都是Integer.MAX_VALUE*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
- CachedThreadPool 和 ScheduledThreadPool :允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
/**
* 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
* 那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。
* 此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
/**
* 创建一个定长线程池,支持定时及周期性任务执行,通过过schedule方法可以设置任务的周期执行
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
5. ThreadPoolExecutor构造函数参数分析
- corePoolSize : 核心线程数。定义了最小可以同时运行的线程数量,即使空闲也不会被回收销毁。
- maximumPoolSize : 最大线程数。当队列中存放的任务数量达到队列容量时,当前可以同时运行的线程数量变为最大线程数。
- keepAliveTime:存活时间。当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
- unit : 存活时间的单位。keepAliveTime 参数的时间单位,秒,毫秒,微秒,纳秒等。
- workQueue: 任务队列。任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到,就会被存放在队列中。
- threadFactory :线程工厂类。用于在需要的时候生成新的线程。默认实现是Executors.defaultThreadFactory(),即new 一个Thread对象,并设置线程名称,daemon等属性。
- handler :饱和策略。当提交任务时既没有空闲线程,任务队列也满了,这时候就会根据饱和策略做出相应处理。
6. ThreadPoolExecutor 饱和策略
定义:如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolTaskExecutor 定义一些策略来做出相应的处理。
- ThreadPoolExecutor.AbortPolicy:默认的饱和策略,中止任务,该策略将抛出RejectedExecutionException。调用者可以捕获这个异常然后去编写代码处理异常。
- ThreadPoolExecutor.CallerRunsPolicy:该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。它不会在线程池的某个线程中执行新提交的任务,而是在一个调用了execute的线程中执行该任务。
- ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉,也不会抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy: 丢弃最早的未处理的任务请求,也就是最先进入队列中的任务。
7.任务队列的可选类别
8.线程池的五种状态
线程池各个状态切换框架图
- RUNNING
状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
状态切换:线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。
- SHUTDOWN
状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
- STOP
状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
- TIDYING
状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
- TERMINATED
状态说明:线程池彻底终止,就变成TERMINATED状态。
状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。