通过Executors线程池工具类来使用:
- Executors.newSingleThreadExecutor():创建只有一个线程的线程池
- Executors.newFixedThreadPool(int):创建固定线程的线程池
- newScheduledThreadPool 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
- Executors.newCachedThreadPool():创建一个可缓存的线程池,线程数量随着处理业务数量变化。
- 使用Executors.newCachedThreadPool()创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newCachedThreadPool 将 corePoolSize 设置为0,将 maximumPoolSize 设置为 Integer.MAX_VALUE,使用的 SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
线程池参数
- corePoolSize: 线程池中的常驻核心线程数,可理解为初始化线程数
- maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
- threadFactory:线程工厂;表示生成线程池中工作线程的线程工厂,用于创建线程,一般用默认的即可
- workQueue:任务队列;随着业务量的增多,线程开始慢慢处理不过来,这时候需要放到任务队列中去等待线程处理
- rejectedExecutionHandler:拒绝策略;如果业务越来越多,线程池首先会扩容,扩容后发现还是处理不过来,任务队列已经满了,这时候拒绝接收新的请求
- keepAliveTime:多余的空闲线程的存活时间;如果线程池扩容后,能处理过来,而且数据量并没有那么大,用最初的线程数量就能处理过来,剩下的线程被叫做空闲线程
- unit:多余的空闲线程的存活时间的单位
在创建了线程池后,等待提交过来的任务请求;当调用execute方法添加一个请求任务时,线程池会做如下判断:
- 如果当前运行的线程数小于corePoolSize,那么马上创建线程运行该任务
- 如果当前运行的线程数大于等于corePoolSize,那么该任务会被放入任务队列
- 如果这时候任务队列满了且正在运行的线程数还小于maximumPoolSize,那么要创建非核心线程立刻运行这个任务(扩容)
- 如果任务队列满了且正在运行的线程数等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行
- 随着时间的推移,业务量越来越少,线程池中出现了空闲线程,当一个线程无事可做超过一定的时间时,线程池会进行判断:
- 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉,所以线程池的所有任务完成后它最终会收缩到 corePoolSize 的大小
四种拒绝策略
在线程池中,如果任务队列满了并且正在运行的线程个数大于等于允许运行的最大线程数,那么线程池会启动拒绝策略来执行,具体分为下列四种:
- AbortPolicy: 默认拒绝策略;直接抛出java.util.concurrent.RejectedExecutionException异常,阻止系统的正常运行;
- CallerRunsPolicy:调用这运行,一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量;
- DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中;
- DiscardPolicy:直接丢弃任务,不给予任何处理也不会抛出异常;如果允许任务丢失,这是一种最好的解决方案;
合理配置线程池参数
合理配置线程池参数,可以分为以下两种情况:
- CPU密集型:CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行;
CPU密集型任务配置尽可能少的线程数量:参考公式:(CPU核数+1)
- IO密集型:即该任务需要大量的IO,即大量的阻塞;
在IO密集型任务中使用多线程可以大大的加速程序运行,故需要多配置线程数:参考公式:CPU核数/ (1-阻塞系数) 阻塞系数在0.8~0.9之间