什么是线程池
线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如MySQL。
线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。
本文学习记录的线程池是JDK中提供的ThreadPoolExecutor类。
那些地方用到线程池
实际开发中项目中,禁止自己new线程。必须使用线程池来维护和创建线程。
线程池的作用
核心:复用机制。提前创建好固定的线程一直在运行状态,实现复用,限制线程创建数量。
使用线程池可以带来一系列好处:
- 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
- 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
线程池的创建
例子:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Threadpool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int finalI = i;
executorService.execute((new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+","+finalI);
}
}));
}
}
}
//输出:
pool-1-thread-2,1
pool-1-thread-6,5
pool-1-thread-5,4
pool-1-thread-4,3
pool-1-thread-3,2
pool-1-thread-8,7
pool-1-thread-1,0
pool-1-thread-7,6
pool-1-thread-9,8
pool-1-thread-10,9
//上面程序发现没有复用,查看源码发现可以创建MAX_VALUE的线程,不能体现特点。
//改用Executors.newFixedThreadPool(),可以解决
ThreadPoolExecutor核心参数有哪些
corePoolSize:核心线程数量 一直保持运行的线程
线程池中的核心线程数,默认情况下核心线程一直存活在线程池中,如果将ThreadPoolExecutor 的allowCoreThreadTimeOut 属性设为true,如果线程池一直闲置并超过了keepAliveTime 所指定的时间,核心线程就会被终止。
maximumPoolSize:最大线程数,线程池允许创建的最大线程数
keepAliveTime:超出corePoolSize后创建的线程的存活时间。
unit:keepAliveTime时间单位
workQueue:任务队列,用于保存待执行的任务。
threadFactory:线程池内部创建线程所用的工厂。
handler:任务无法执行时的处理器。
线程池底层原理
线程池任务调度
所有任务的调度都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。其执行过程如下:
- 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
- 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
- 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
线程池创建的线程会一直在运行状态吗
不会。例如:配置核心线程数corePoolSize为2、最大线程数maximumPoolSize为5,我们可以通过配置超出corePoolSize核心线程数后创建的线程存活时间例如60s。在60s内没有核心线程一直没有任务执行,则会停止该线程。
为什么阿里巴巴不建议使用Executors
因为默认的Executors线程池底层是基于ThreadPoolExecutor构造函数封装的,采用无界队列存放缓存任务,会一直缓存任务容易发生内存溢出,会导致我们最大线程数失效。
ThreadPoolExecutor底层实现原理
手写线程池
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
public class MyExecutors {
private List<WorkThread> workThreads;
private BlockingDeque<Runnable> runnableDeque;
private boolean isRun = true;
/*
最大线程数
@param maxThreadCount
*/
public MyExecutors(int maxThreadCount, int dequeSize){
//1.限制队列容量缓存
runnableDeque = new LinkedBlockingDeque<Runnable>(dequeSize);
//2.提前创建好固定的的线程一直在运行状态---死循环实现
new ArrayList<WorkThread>(maxThreadCount);
for (int i = 0; i < maxThreadCount; i++) {
new WorkThread().start();
}
}
class WorkThread extends Thread{
public void run(){
while (isRun || runnableDeque.size()>0){
Runnable runnable = runnableDeque.poll();
if (runnable != null){
runnable.run();
}
}
}
}
public boolean execute(Runnable command){
return runnableDeque.offer(command);
}
public static void main(String[] args) {
MyExecutors myExecutors = new MyExecutors(2,2);
for (int i = 0; i < 10; i++) {
final int finalI = i;
myExecutors.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"," + finalI);
}
});
}
myExecutors.isRun = false;
}
}
实际最多执行多少个任务 核心线程数+缓存队列的容量+最大线程数-核心线程数
线程池拒绝策略类型有哪些
1.AbortPolicy 丢弃任务,抛运行时异常
2.CallerRunsPolicy 执行任务
3.DiscardPolicy 忽视,什么都不会发生
4.DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
5.实现 RejectedExecutionHandler 接口,可自定义处理器
线程池状态和生命周期
参考资料
全面学习可参考: