为什么要用线程池
- 线程在Java中也是一个对象,更是操作系统的资源。线程的创建、销毁需要时间。如果创建时候+销毁时间>执行任务时间就很不划算。
- Java对象占用堆内存,操作系统线程占用系统内存,根据JVM规范,一个线程最大栈大小1M,这个栈空间是需要从系统内存中分配的。线程过多,会消耗很多的内存。
- 操作系统需要频繁切换线程上下文(大家都想被运行),影响性能。
- 线程池也能方便的控制线程数量。
线程池原理 - 概念
- 线程池管理器:用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务。
- 工作线程:线程池线程,在没有任务时处于等待状态,可以循环的执行任务。
- 任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。
- 任务队列:用于存放没有处理的任务。提供一种缓冲机制。
线程池原理 - 任务execute过程
- 是否达到核心线程数量?没达到,创建一个工作线程来执行任务。
- 工作队列是否已满?没满,则将新提交的任务存储在工作队列里。
- 是否达到线程池最大数量?没达到,则创建一个新的工作线程来执行任务。
- 最后,执行拒绝策略来处理这个任务。
线程池API - 接口定义和实现类
可以认为ScheduledThreadPoolExecutor是最丰富的的实现类
- 线程池基本API:
1、Executor接口:最上层接口,定义了execute方法。
2、ExecutorService接口:继承了Executor接口,扩展了submit方法(Callable、Future),shutdown方法等。
3、ScheduledExecutorService接口:继承了ExecutorService接口,扩展了schedule(一次性)、scheduleAtFixedRate(延迟不等待)、scheduleWithFixedDelay(延迟也等待)三个方法。
4、AbstractExecutorService模板类:实现了ExecutorService接口中部分方法(submit)。
5、ThreadPoolExecutor类:基础标准的线程池实现(execute)。
6、ScheduledThreadPoolExecutor类:继承ThreadPoolExecutor方法、实现了ScheduledExecutorService接口,扩展了定时任务方法,并重写了execute、submit方法。
线程池API - 方法定义
Executors工具类
newFixedThreadPool(int nThreads):创建一个固定大小、任务队列容量无界的线程池。核心线程数=最大线程数。
newCachedThreadPool():创建的是一个大小无界的缓冲线程池。它的任务队列是一个同步队列。任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如无则创建新线程执行。池中的线程空闲超过60秒,将被销毁释放。线程数随任务的多少变化。适用于执行消耗较小的异步任务。池的核心线程数=0,最大线程数=Integer.MAX_VALUE。
newSingleThreadExecutor():只有一个线程来执行无界任务队列。该线程池确保任务按加入的顺序一个一个依次执行。但唯一的线程因任务异常中止时,将创建一个新的线程来继续执行后续的任务。与newFixedThreadPool(1)的区别在于,单一线程池的池大小在newSingleThreadExecutor方法中硬编码,不能再改变的。
newScheduledThreadPool(int corePoolSize):能定时执行任务的线程池。该池的核心线程数由参数指定,最大线程数=Integer.MAX_VALUE。
Executors工具类:newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool等,但阿里巴巴JAVA开发手册最好都自己创建线程池。
代码实例
-
最小线程数+最大线程数+无界队列线程池
线程池永远达不到最大线程数,超出最小线程直接往队列丢,超出最小线程数的线程存活时间只有5秒
-
最小线程数+最大线程数+有界队列线程池
超过最小线程会往队列里面添加,队列也满了就会创建线程往线程池里丢,直到线程池数量达到最大线程数
-
最小线程数+无界队列线程池
会一直接受新的任务,直到达到操作系统CPU和内存的上限
-
缓存线程池
线程池数量为0,所有新建线程往队列里丢,直到达到队列最大容量
-
一次性定时任务
3秒执行一次
-
循环定时任务
scheduleAtFixedRate与scheduleWithFixedDelay相比,前者如果线程执行时间大于间隔时间会执行完成后立即执行堆积的任务,后者无论如何都会按间隔时间执行。
-
终止线程
shutdown方法会等线程池及队列中的所有线程执行完毕后结束。
shutdownNow会立即终止线程池及队列中的所有线程,并且返回没有完成的线程数量。
线程数量
如何确定合适数量的线程?
计算型任务:计算型任务:线程数=CPU数目 * 2。
IO密集型任务:CPU运行效率要达到80%。
不确定线程数量、不占用空闲线程:用缓存性线程池