使用线程池的原因
线程是不是越多越好?
- 线程在java中是一个对象,创建销毁都需要时间开销,如果比任务执行时间都长,就不合算
- java对象占用堆内存,线程太多会占用很多系统内存
- 操作系统需要频繁切换线程(大家都想被运行),影响性能
所以,线程池的推出,就是为了方便的控制线程数量!
线程池相关概念
1、线程池管理器
用于创建并管理线程池,包括线程池的创建,销毁,添加新任务
2、工作线程
线程池中的线程,并未有任务时处于等待状态,可以循环执行任务
3、任务接口
每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务完成的收尾工作,任务的执行状态等
4、任务队列
用于存放没有处理的任务,提供了一种缓冲机制
线程池API - 接口定义和实现类
ExcutorService 中的方法
ScheduledExcutorService 中的补充方法
理解:
- 第三个方法的调度方式:如果方法执行时间 > 固定间隔, 则以执行时间为主,忽略固定间隔
- 第四个方法的调度方式:如果方法执行时间 > 固定间隔,则仍然以固定间隔为主
Excutors工具类
使用:(以下两种方式是等价的!)
- Executors.newFixedThreadPool( 5)
- ThreadPoolExcutor threadPoolExcutor = new ThreadPoolExcutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
一、newFixedThreadPool(int nThreads)
创建一个固定大小,任务队列无界的线程池。核心线程数 = 最大线程数
二、newCachedThreadPool()
- 创建一个大小无界的缓冲线程池
- 它的队列是一个同步队列
- 任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如果没有,则创建新线程执行
- 池中的线程空闲超过60s将被销毁释放
- 线程数随任务的多少而变化
- 适用于执行耗时较小的异步任务
- 池的核心线程数 = 0,最大线程数 = Integer.MAX_VALUE
三、newSingleThreadExcutor()
- 只有一个线程来执行无界队列的单一线程池
- 该线程池确保任务按加入的顺序一个一个依次执行
- 当唯一的线程因为任务异常终止时,将创建一个新的线程来继续执行后续的任务
- 与newFixedThreadPool(1)的区别是,单一线程池的大小是方法内硬编码,不可改变
四、newScheduledThreadPool(int corePoolSize)
- 能定时执行任务的线程池
- 该池的核心线程数又参数指定,最大线程数 = Integer.MAX_VALUE
线程池原理 - 任务excute过程
线程池终止
一、threadPoolExcutor.shutdown()
调用shutdown()方法后,将不接收新的任务,会等待已有任务(队列+最大线程数)全部执行结束后关闭线程池
二、threadPoolExcutor.shutdownNow()
- 调用shutdownNow()后,不管有没有任务在执行,都立刻关闭线程池,没跑完的任务会全部抛出异常
- 返回值为List ,就是还在队列中的任务
线程数量
问题:那如何确定合适数量线程呢?
建议:
- 计算型任务:cpu数量的1 - 2倍
- IO型任务,要根据具体IO阻塞时长进行考量,适当增加线程数(比如tomcat最大是200)
- 也可以根据需要在最小数量与最大数量间自动增减线程数(比如newCachedThreadPool)
达标建议:生产环境CPU使用率达到80%,就算比较合理了