线程池底层原理
1、什么是线程池
线程池和数据库连接池非常类似,可以统一管理和维护线程,减少没有必要的开销。
2、为什么使用线程池
频繁的开启线程或者停止线程,线程需要重新被 cpu 从就绪到运行状态调度,需要发生 cpu 的上下文切换,效率非常低。
在阿里巴巴开发手册中也谈到,线程资源必须通过线程池提供,不允许在应用中自行显示创建线程
线程池的好处是减少在创建销毁线程上所消耗的时间和系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题
3、线程池有哪些作用
统一管理线程,核心就是
复用机制(线程池通过提前创建几个线程一直在运行状态,限制好了线程的数目)
线程池创建好的线程 一直在运行不会停止,会导致CPU飙高
- .降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
4、线程池底层如何是如何实现复用的
手写线程池-- 多线程的思想、生产者消费者模型
- 阻塞队列
- 线程池封装(核心思想为复用机制)
做只会创建2个线程,提交10个线程任务(涉及到缓存)
- 定义一个容器LinkedBlockingQueue缓存提交的线程任务
- 提前创建好固定数量的线程一直在运行状态(5个线程)
- 无界(无限存储)有界(有限制容量存储元素)
- 提交线程任务放在LinkedBlockingQueue中缓存起来
- 一直在运行的线程就会从LinkedBlockingQueue获取线程任务执行
public class BeauExecutors {
//1. 定义一个容器LinkedBlockingQueue缓存提交的线程任务
private LinkedBlockingDeque<Runnable> runnables;
/**
*
* @param queSize 队列缓存任务容量的大小
* @param runThreadCount 运行线程个数
*/
public BeauExecutors(int queSize,int runThreadCount){
runnables=new LinkedBlockingDeque<>(queSize);
for(int i=0;i<runThreadCount;i++){
new TaskThread().start();
}
}
/**
* 提交线程任务
* @param target
*/
void execute(Runnable target){
runnables.offer(target);
}
class TaskThread extends Thread{
@Override
public void run() {
//线程一直在运行
while(true){
//5. 一直在运行的线程就会从LinkedBlockingQueue获取线程任务执行
Runnable poll = runnables.poll();
if(poll!=null){
poll.run();
}
}
}
}
}
5、如何停止线程池
定义一个boolean类型的变量进行控制
//标记停止线程
private volatile boolean isRun=true;
//停止当前线程池,
public void shutdown(){
isRun=false;
}
class TaskThread extends Thread{
@Override
public void run() {
//这里可以保障任务队列的全部任务全部运行完在关闭线程池
while(isRun||runnables.size()>0){
Runnable poll = runnables.poll();
if(poll!=null){
poll.run();
}
}
}
}
我们在运行的时候出现CPU飙高的情况
解决CPU飙高
new Thread(()->{ while (true){ //CPU飙高 Thread.sleep(10); //加上sleep不会发生CPU飙高 } }).start(); //so class TaskThread extends Thread{ @Override public void run() { //这里可以保障任务队列的全部任务全部运行完在关闭线程池 while(isRun||runnables.size()>0){ Runnable poll = runnables.poll(3, TimeUnit.SECONDS); if(poll!=null){ poll.run(); } } } }
6、为什么不推荐使用JDK自带的Executors创建线程池
因为默认的 Executors 线程池底层是基于 ThreadPoolExecutor 构造函数封装的,采用无界队列存放缓存任务,会无限缓存任务容易发生 内存溢出,会导致我们最大线程数会失效。
7,线程池队列满了拒绝策略
自定义拒绝异常
线程池会调用rejectedExecutionHandler处理这个任务,如果没有设置,默认采用AbortPolicy,会抛出异常
ThreadPoolExecutor类有几个内部实现类处理拒绝任务
AbortPolicy 丢弃认为,并抛出异常
CallerRunsPolicy 执行任务
哪个线程执行了该任务?:用调用者所在的线程来执行任务
DiscardPolicy 忽视,什么不用管
DiscardOldestPolicy 从队列中踢出最先进入队列的任务
实现RejectedExecutionHandler接口,自定义处理器
队列满的话,可以将任务记录到本地磁盘(日志)或者网络中保存,后期可以人工补偿