1.什么是线程池?
线程池和数据库连接池非常类似,可以统一管理和维护线程,减少没有必要的开销。
2.为什么要使用线程池
因为频繁的开启线程或者停止线程,线程需要从新被cpu从就绪到运行状态调度,需要发生
cpu的上下文切换,效率非常低。
3.实际开发中哪些地方会使用到线程池?
接收短信、邮件等
注意:实际开发项目项目中,禁止自己new线程,必须使用线程池来维护和创建线程
4.线程池有哪些作用?
核心点:复用机制 提前创建好固定的线程一直在运行状态 实现复用 限制线程创建数量。
降低资源消耗
提高响应速度
提高线程的可管理性
允许任务延期执行或定期执行
5.线程池的创建方式
原始jdk文档有四种创建方式:
Executors.newCachedThreadPool();可缓存线程池)
Executors.newFixedThreadPool();可定长度 限制最大线程数,有时候会使用的到
Executors.newScheduledThreadPool(); 可定时
Executors.newSingleThreadExecutor();单例
底层都是基于 ThreadPoolExecutor 构造函数封装,实际开发中一般不适用
6.线程池底层是如何实现复用的
本质思想:创建一个线程,不会立马停止或者销毁而是一直实现复用。
提前创建固定大小的线程一直保持在正在运行状态;(可能会非常消耗cpu的资源)
当需要线程执行任务,将该任务提交缓存在并发队列中;如果缓存队列满了,则会执行拒绝策略;
正在运行的线程从并发队列中获取任务执行从而实现多线程复用问题;
线程池核心点:复用机制------
1.提前创建好固定的线程一直在运行状态----死循环实现
2.提交的线程任务缓存到一个并发队列集合中,交给我们正在运行的线程执行
3.正在运行的线程就从队列中获取该任务执行
简单模拟手写Java线程池:
public classMyExecutors{
publicBlockingDeque<Runnable>runnables;
private volatileBooleanisRun=true;
/**
* dequeSize缓存任务大小
*
* @paramdequeSize
* @paramthreadCount复用线程池
*/
publicMyExecutors(intdequeSize,intthreadCount) {
runnables=newLinkedBlockingDeque<Runnable>(dequeSize);
for(inti =0; i < threadCount; i++) {
WorkThread workThread=newWorkThread();
workThread.start();
}
}
public voidexecute(Runnablerunnable) {
runnables.offer(runnable);
}
classWorkThreadextendsThread{
@Override
public voidrun() {
while(isRun||runnables.size()>0) {
Runnable runnable=runnables.poll();
if(runnable!=null) {
runnable.run();
}
}
}
}
public static voidmain(String[] args) {
MyExecutors myExecutors=newMyExecutors(10,2);
for(inti =0; i <10; i++) {
final intfinalI= i;
myExecutors.execute(newRunnable() {
@Override
public voidrun() {
System.out.println(Thread.currentThread().getName() +":,"+finalI);
}
});
}
myExecutors.isRun=false;
}
}
7.ThreadPoolExecutor核心参数有哪些
corePoolSize:核心线程数量 一直正在保持运行的线程
maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
keepAliveTime:超出corePoolSize后创建的线程的存活时间。
unit:keepAliveTime的时间单位。
workQueue:任务队列,用于保存待执行的任务。
threadFactory:线程池内部创建线程所用的工厂。
handler:任务无法执行时的处理器。
8.线程池创建的线程会一直在运行状态吗?
答:不会
例如:配置核心线程数corePoolSize为2、最大线程数maximumPoolSize为5
我们可以通过配置超出corePoolSize核心线程数后创建的线程的存活时间例如为60s
在60s内没有核心线程一直没有任务执行,则会停止该线程。
9.为什么阿里巴巴不建议使用Executors?
因为默认的 Executors 线程池底层是基于 ThreadPoolExecutor
构造函数封装的,采用无界队列存放缓存任务,会无限缓存任务容易发生
内存溢出,会导致我们最大线程数会失效。
10.线程池底层ThreadPoolExecutor底层实现原理
当线程数小于核心线程数时,创建线程。
当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
当线程数大于等于核心线程数,且任务队列已满
> 若线程数小于最大线程数,创建线程
> 若线程数等于最大线程数,抛出异常,拒绝任务
11.线程池队列满了,任务会丢失吗
如果队列满了,且任务总数>最大线程数则当前线程走拒绝策略。
可以自定义异拒绝异常,将该任务缓存到redis、本地文件、mysql中后期项目启动实现补偿。
AbortPolicy丢弃任务,抛运行时异常
CallerRunsPolicy执行任务
DiscardPolicy忽视,什么都不会发生
DiscardOldestPolicy从队列中踢出最先进入队列(最后一个执行)的任务
实现RejectedExecutionHandler接口,可自定义处理器
12.线程池拒绝策略类型有哪些呢
1.AbortPolicy丢弃任务,抛运行时异常
2.CallerRunsPolicy执行任务
3.DiscardPolicy忽视,什么都不会发生
4.DiscardOldestPolicy从队列中踢出最先进入队列(最后一个执行)的任务
5.实现RejectedExecutionHandler接口,可自定义处理器
13.线程池如何合理配置参数
自定义线程池就需要我们自己配置最大线程数maximumPoolSize,为了高效的并发运行,当
然这个不能随便设置。这时需要看我们的业务是IO密集型还是CPU密集型。
CPU密集型
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。CPU密集任
务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几
个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。
CPU密集型任务配置尽可能少的线程数量:以保证每个CPU高效的运行一个线程。
一般公式:(CPU核数+1)个 线程的线程池
IO密集型
I0密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行I0密集型的任务会导
致浪费大量的CPU运算能力浪费在等待。
所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加
速主要就是利用了被浪费掉的阻塞时间。
I0密集型时,大部分线程都阻寒,故需要多配置线程数:
公式:
CPU核数* 2
CPU核数/(1 -阻塞系数) 阻塞系数 在0.8~0.9之间
查看CPU核数:
System.out.println(Runtime.getRuntime().availableProcessors());