1.为什么要用线程池
如果每个程序请求到达就创建一个新线程,那么创建和销毁线程花费的时间和消耗的系统资源都相当大,如果在一个 Jvm 里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了解决这个问题,就有了线程池的概念,线程池的核心是提前创建好若干个线程放在一个容器中。如果有任务需要处理,则将任务直接分配给线程池中的线程来执行就行,任务处理完以后这个线程不会被销毁,而是等待后续分配任务。
java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池.多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。如果:T1 + T3 远大于 T2,则可以采用线程池。线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目
2.线程池的优势
- 降低创建线程和销毁线程的性能开销
- 提高响应速度,当有新任务需要执行是不需要等待线程创建就可以立马执行
- 合理的设置线程池大小可以避免因为线程数超过硬件资源瓶颈带来的问题
3.线程池的组成
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
4.线程池的类别
newFixedThreadPool | 返回一个固定数量的线程池,线程数不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个任务队列中,等待有空闲的线程去执行。 |
newSingleThreadExecutor | 创建一个只有唯一线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中。 |
newCachedThreadPool | 返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若用空闲的线程则执行任务,若无任务则不创建线程。并且每一个空闲线程会在 60 秒后自动回收 |
newScheduledThreadPool | 创建一个可以指定线程的数量的线程池,但是这个线程池还带有延迟和周期性执行任务的功能,类似定时器。 |
5、线程池中的线程初始化
线程池中的线程初始化 默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。 在实际中如果需要 线程池创建之 后立即创建线 程,可以通过 以下两个方法 办到:
prestartCoreThread():初始化一个核心线程; prestartAllCoreThreads():初始化所有核心线 程
ThreadPoolExecutor tpe=(ThreadPoolExecutor)service;
tpe.prestartAllCoreThreads();
6、线程池的关闭
ThreadPoolExecutor 提 供 了 两 个 方 法 , 用 于 线 程 池 的 关 闭 , 分 别 是 shutdown() 和 shutdownNow(),
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中 的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并 尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
7.线程池状态:
线程池状态 | 解释 | 状态转换 |
RUNNING(-1) | 接收新任务,并执行队列中的任务 |
RUNNING---> SHUTDOWN : shutdown() RUNNING--->STOP : shutdownnow()
|
SHUTDOWN(0) | 不接收新任务,但是执行队列中的任务 | SHUTDOWN --->TIDYING: 阻塞队列未空,线程池中的核心线程数量为0 |
STOP(1) | 不接收新任务,不执行队列中的任务,中断正在执行中的任务 | stop--->TIDYING 线程池中的核心线程数量为0 |
TIDYING (2) | 所有的任务都已结束, 线程数量为 0,处于该状态的线程池即将调用 terminated()方法 | TIDYING--->TERMINATED : terminated() |
TERMINATED(3) | // terminated()方法 执行完成 |
9.线程池大小设计
- 需要分析线程池执行的任务的特性: CPU 密集型(cpu 核心数+1)还是 IO 密集型 (((线程池设定的线程等待时间+单个线程 CPU 时间)/ 单个线程 CPU 时间 )* CPU 数目)
- 每个任务执行的平均时长大概是多少,这个任务的执行时长可能还跟任务处理逻辑是否涉 及到网络传输以及底层系统资源依赖有关系
10、线程池的监控
如果在项目中大规模的使用了线程池,那么必须要有一套监控体系,来指导当前线程池的状态,线程池提供了相应的扩展方法,我们通过重 写线程池的 beforeExecute、afterExecute 和 shutdown 等方式就可以实现对线程的监控
public class Demo1 extends ThreadPoolExecutor {
// 保存任务开始执行的时间,当任务结束时,用任务结束时间减去开始时间计算任务执行时间
private ConcurrentHashMap<String,Date> startTimes;
public Demo1(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
this.startTimes=new ConcurrentHashMap<>();
}
@Override
public void shutdown() {
System.out.println("已经执行的任务数:"+this.getCompletedTaskCount()+"," +"当前活动线程数:"+this.getActiveCount()+",当前排队线程数:"+this.getQueue().size());
System.out.println();
super.shutdown();
}
//任务开始之前记录任务开始时间
@Override
protected void beforeExecute(Thread t, Runnable r) {
startTimes.put(String.valueOf(r.hashCode()),new Date());
super.beforeExecute(t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
Date startDate = startTimes.remove(String.valueOf(r.hashCode()));
Date finishDate = new Date(); long diff = finishDate.getTime() -
startDate.getTime();
// 统计任务耗时、初始线程数、核心线程数、正在执行的任务数量、
// 已完成任务数量、任务总数、队列里缓存的任务数量、
// 池中存在的最大线程数、最大允许的线程数、线程空闲时间、线程池是否关闭、线程池是否终止
System.out.print("任务耗时:"+diff+"\n");
System.out.print("初始线程数:"+this.getPoolSize()+"\n");
System.out.print("核心线程数:"+this.getCorePoolSize()+"\n");
System.out.print("正在执行的任务数量:"+this.getActiveCount()+"\n");
System.out.print("已经执行的任务数:"+this.getCompletedTaskCount()+"\n");
System.out.print("任务总数:"+this.getTaskCount()+"\n");
System.out.print("最大允许的线程数:"+this.getMaximumPoolSize()+"\n");
System.out.print("线程空闲时间:"+this.getKeepAliveTime(TimeUnit.MILLISECONDS)+"\n");
System.out.println();
super.afterExecute(r, t);
}
public static ExecutorService newCachedThreadPool() {
return new Demo1(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue ());
}
}