一、线程池介绍
1.什么是线程池
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象;
2.为什么需要使用线程池
使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力;
3.使用线程池由哪些优势
1:线程和任务分离,提升线程重用性;
2:控制线程并发数量,降低服务器压力,统一管理所有线程;
3:提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间;
4.线程的应用场景
1.云盘文件上传和下载
2.多线程批量发送短信邮件
3.框架内部很多都使用多线程提高效率
二、线程池工作流程
ThreadPoolExecutor参数详解
/*
核心线程的个数:默认情况下不会被回收
最大线程数:当任务比较繁忙(核心线程数在处理任务,其他任务会先被放入到阻塞队列,这时候会创建新的线程,直到达到最大线程数);
非核心线程数空闲时间:在任务繁忙的时候会创建非核心线程(最大线程数-核心线程的个数),在空闲的时候回收,等待设置空闲,如果都没有任务,就会把线程回收(就是线程执行完后把线程销毁)
阻塞队列:当核心线程都在处理任务的情况,新加入的任务会被放入到队列中,等待线程获取
线程工厂:线程内部使用的线程是怎么创建的
拒绝策略:阻塞队列已经装满了,核心线程数已经到达最大线程数,新加入的任务执行拒绝策略。
1.AbortPolicy 直接抛出异常
2.DiscardPolicy 直接丢弃任务
3.DiscardOldestPolicy 把阻塞队列中最前面的元素丢弃,然后任务放到队尾
4.CallerRunsPolicy 谁调用线程池就由该线程执行这个任务,比如main线程调用线程池,由,main线程执行该任务
*/
//ThreadPoolExecutor("核心线程的个数","最大线程数","非核心线程数空闲时间","时间单位","阻塞队列","线程工厂","拒绝策略")
ThreadPoolExecutor executor=new ThreadPoolExecutor(2,4,60, TimeUnit.SECONDS,
new ArrayBlockingQueue(10), new ThreadFactory(){
int count;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"小米"+count++);
}
}, new ThreadPoolExecutor.DiscardPolicy());
for (int i = 0; i <16; i++){
int x=i;
executor.execute(new Runnable(){
@Override
public void run(){
System.out.println("线程名称:"+Thread.currentThread().getName()+",顺序:"+x);
}
});
}
核心线程数(corePoolSize)
表示线程池中的核心线程的数量,也可以称为可闲置的线程数量,默认情况下不会被回收
最大线程数(maximumPoolSize)
非核心线程存活时间(keepAliveTime)
但是创建的“临时”线程是有存活时间的,不可能让他们一直都存活着,当阻塞队列中的任务被执行完毕,并且又没有那么多新任务被提交时,“临时”线程就需要被回收销毁,在被回收销毁之前等待的这段时间,就是非核心线程的存活时间,也就是 keepAliveTime 属性。
缓存任务的阻塞队列(BlockingQueue)
当线程池接收到一个任务时,如果工作线程数没有达到corePoolSize,那么就会新建一个线程,并绑定该任务,直到工作线程的数量达到 corePoolSize 前都不会重用之前的线程。
当工作线程数达到 corePoolSize 了,这时又接收到新任务时,会将任务存放在一个阻塞队列中等待核心线程去执行。
创建线程的工厂(ThreadFactory)
既然是线程池,那自然少不了线程,线程该如何来创建呢?这个任务就交给了线程工厂 ThreadFactory 来完成。
拒绝策略(RejectedExecutionHandler)
AbortPolicy
丢弃任务并抛出RejectedExecutionException异常。
DiscardPolicy
也是丢弃任务,但是不抛出异常。
DiscardOldestPolicy
丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
CallerRunsPolicy
由调用线程处理该任务
绘图演示每个参数意思
线程池工作流程图
参数设计分析
核心线程数(corePoolSize)
核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定,例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程,此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均,所以我们一般按照8020原则设计即可,既按照百分之80的情况设计核心线程数,剩下的百分之20可以利用最大线程数处理;
经验:
cpu密集型项目 : cpu核心数+1
io密集型项目: 2*cpu核心数
任务队列长度(workQueue)
任务队列长度一般设计为:核心线程数/单个任务执行时间*2即可;例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200;
最大线程数(maximumPoolSize)
最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定:例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,最大线程数=(最大任务数-任务队列长度)*单个任务执行时间;既: 最大线程数=(1000-200)*0.1=80个;
最大空闲时间(keepAliveTime)
这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可;
线程池源码剖析
线程池的五种状态
RUNNING
运行状态,该状态下线程池可以接受新的任务,也可以处理阻塞队列中的任务
执行 shutdown 方法可进入 SHUTDOWN 状态
执行 shutdownNow 方法可进入 STOP 状态
SHUTDOWN
待关闭状态,不再接受新的任务,继续处理阻塞队列中的任务
当阻塞队列中的任务为空,并且工作线程数为0时,进入 TIDYING 状态
STOP
停止状态,不接收新任务,也不处理阻塞队列中的任务,并且会尝试结束执行中的任务
当工作线程数为0时,进入 TIDYING 状态
TIDYING
整理状态,此时任务都已经执行完毕,并且也没有工作线程
执行 terminated 方法后进入 TERMINATED 状态
TERMINATED
终止状态,此时线程池完全终止了,并完成了所有资源的释放
线程池状态和工作线程数量
ThreadPoolExecutor 中只用了一个 AtomicInteger 型的变量就保存了线程池状态和工作线程数量这两个属性的值,那就是 ctl。
线程池工具类(ExecutorService)
ExecutorService接口是java内置的线程池接口,通过学习接口中的方法,可以快速的掌握java内置线程池的基本使用
方法
shutdown()
关闭线程池,调用该方法之后,后续添加任务是不会执行了,但是已经加入的任务会继续执行完。
shutdownNow()
关闭线程池,调用该方法之后,后续添加任务是不会执行了,已经加入的任务如果没有开始就不会再执行了。
submit(Callable<T> task)
执行带返回值的任务,返回一个Future对象。
submit(Runnable task)
执行 Runnable 任务,并返回一个表示该任务的 Future。
submit(Runnable task, T result)
执行 Runnable 任务,并返回一个表示该任务的 Future。
实现类
Executors.newCachedThreadPool()
创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建
Executors.newFixedThreadPool()
创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor()
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
Executors.newScheduledThreadPool()
创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务;
schedule(Callable<V> callable, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行callable。
schedule(Runnable command, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行command。
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
延迟时间单位是unit,数量是initialDelay的时间后,每间隔period时间重复执行一次command。
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
Executors.newSingleThreadScheduledExecutor()
创建一个单线程执行程序,它允许在给定延迟后运行命令或者定期地执行。
异步计算结果(Future)
我们刚刚在学习java内置线程池使用时,没有考虑线程计算的结果,但开发中,我们有时需要利用线程进行一些计算,然后获取这些计算的结果,而java中的Future接口就是专门用于描述异步计算结果的,我们可以通过Future 对象获取线程计算的结果;
cancel(boolean mayInterruptIfRunning)
试图取消对此任务的执行。
get()
如有必要,等待计算完成,然后获取其结果。
get(long timeout, TimeUnit unit)
如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
isCancelled()
如果在任务正常完成前将其取消,则返回 true。
isDone()
如果任务已完成,则返回 true。