目录
四、定时任务线程池ScheduledThreadPoolExecutor常用API
多线程并行处理请求的场景十分常见,如果每次都自己手动创建线程、启动线程、中止线程,业务处理的效率肯定不会很高。线程数量并不是越多越好,因为:1.线程在java中是一个对象,更是操作系统的资源,线程创建、销毁需要时间,如果创建时间+销毁时间>执行任务的时间,就很不划算;2.java对象占用堆内存,操作系统线程占用系统内存,根据jvm规范,一个线程默认最大栈大小为1M,这个栈空间是需要从系统内存中分配的,线程过多,会消耗很多的内存;3.操作系统需要频繁切换线程上下文,影响性能。所以聪明的程序员发明了线程池这个东西,线程池的目的就是为了帮你创建线程、管理线程,将大部分的时间用于真正的业务处理上。
一、线程池的相关概念
线程池管理器:用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务。
工作线程:线程池中的线程,在没有任务时,处于等待状态,可以循环的执行任务。
任务接口:每个任务必须实现的接口,以供工作线程调度任务时执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。
任务队列:用于存放没有处理的任务,提供一种缓冲机制。
![](https://i-blog.csdnimg.cn/blog_migrate/5452b9bf1de6dfcb6ec63e80ec39bf68.png)
二、Java中线程池相关接口及实现类
类型 | 名称 | 描述 |
接口 | Executor | 最上层的接口,定义了执行任务的方法execute |
接口 | ExecutorService | 继承了Executor,扩展了Callable、Future、关闭方法。 |
接口 | ScheduledExecutorService | 继承了ExecutorService,增加了定时任务相关的方法。 |
实现类 | ThreadPoolExecutor | 基础、标准的线程池实现 |
实现类 | ScheduledThreadPoolExecutor | 继承了ThreadPoolExecutor,实现了ScheduledExecutorService中定时任务相关的方法。 |
![](https://i-blog.csdnimg.cn/blog_migrate/a1e1ac58867627b4f75314509efbd5a8.png)
三、线程池的工作原理
通过构造方法ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler),创建线程池时,需要注意传递的各参数的含义。
corePoolSize:核心线程数;
maximumPoolSize:最大线程数;
keepAliveTime:非核心线程最大空闲时间,超过空闲时间将被销毁;
unit:非核心线程最大空闲时间的单位;
workQueue:任务队列;
handler:任务被拒绝时的执行器。
当一个任务交给线程池执行的时候,首先如果有核心线程空闲,则交给核心线程执行,如果所有核心线程都有任务在执行,则放入任务队列中,如果任务队列满了,放不进去了,就会创建非核心线程,让非核心线程执行任务,如果非核心线程+核心线程的数量超过了最大线程数,则会拒绝执行,执行拒绝策略。当创建的非核心线程执行完任务后,进入空闲状态,如果空闲时间超过限定的时间后依然没有任务可执行则会被销毁。流程图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/a0ad1451aac02c4f34c64ce75240e3f7.png)
四、定时任务线程池ScheduledThreadPoolExecutor常用API
构造方法:public ScheduledThreadPoolExecutor(int corePoolSize){...}
提交任务(定时执行):schedule(Callable<V> callable, long delay, TimeUnit unit)
提交任务时可以指定多久之后才执行任务;
周期任务(周期执行):scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
以固定的周期执行任务,如果任务执行的时间超过设定的周期,则上一个任务执行完后立即执行下一个任务。即当任务执行时间小于设定的周期时,任务执行周期=设定的周期;当任务执行时间大于设定的周期时,任务执行周期=任务执行的时间。
周期任务(周期执行):scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit)
以固定的时间间隔执行任务,上一个任务执行完后,需要延迟固定间隔后再执行下一个任务。任务执行的周期=任务执行时间+间隔时间。
![](https://i-blog.csdnimg.cn/blog_migrate/b74612a992f731f614cd6a045b6cf63c.png)
五、创建线程池的API
工具类java.util.concurrent.Executors为我们提供了几种常用的线程池创建方法:
Executors.newFixedThreadPool(int nThread):创建出来的线程池的核心线程数等于最大线程数等于nThread,任务队列为无界队列。即固定几个人在干活,即使来再多的活都可以排队。
Executors.newCachedThreadPool():创建出来的线程池的核心线程数为0,最大线程数等于Integer.MAX_VALUE,同步队列。即我没有仓库,来一个活我请一个临时工(创建一个线程)。
Executors.newSingleThreadExecutor():创建出来的线程池的核心线程数等于最大线程数等于1,任务队列为无界队列。即我单干,不管来多少你先排着。
Executors.newScheduledThreadPool(int corePoolSize)创建出来的线程池的核心线程数为corePoolSize,最大线程数为Integer.MAX_VALUE,任务队列为延时队列DelayedWorkQueue,超出核心线程数量的线程存活时间为0秒。
六、其他注意事项
1、终止线程池:
pool.shutdown();不会接收新任务(会执行拒绝逻辑),但原来的任务会被执行完。
pool.shutdownNow();立即停止,已提交的任务也不会被执行完,已提交正在执行的任务会抛出异常InterruptedException。返回已提交但没被执行的任务集合。
2、线程池如何确定合适的线程数量:
首选分清业务类型,如果是计算型任务,线程数一般设置为CPU数量的1-2倍;如果是IO型任务,等待时间可能较长,则线程数需要设置大一点,比如tomcat默认的最大线程数为200。生产环境下,观察CPU的使用率,达到80%左右是比较合理的。
七、小结
本小节主要讲解了线程池的相关概念、工作原理、常用API及合理线程数量的估算方法。