线程池也是第四冲多线程的创建方式,先来说一下线程池的优势
线程池做的工作主要是控制运行的线程数量
即在处理过程中将任务放入队列,然后在线程创建后自动启动这些任务,如果线程数超过了最大输了,超出数量的线程排队等候,等其他线程执行完毕再从队列中取出
- 线程复用
- 控制最大并发
- 管理线程
值得一提的是,这里的队列用到了阻塞队列BlockingQueue,对于ThreadPool,我们可以理解为已经为我们创建了一堆线程,然后再让这些线程去做我们指定的事情。
Executors创建线程的三种方法
//一池5线程,类似一个银行有5个受理窗口
ExecutorService threadPool1 = Executors.newFixedThreadPool(5);
//一池1线程,类似一个银行有1个受理窗口
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
//一池N线程,类似一个银行有N个受理窗口,可扩展,内存可能溢出
ExecutorService threadPool3 = Executors.newCachedThreadPool();
线程的操作类
for (int i =1; i <=10; i++) {
threadPool.execute(()->{
try {
// TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"办理业务");
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
});
}
newFixedThreadPool
第一个是固定容量的线程池,这个是固定了大小的线程池,每次都是从这个线程池中取的线程。
接着我们加上一句线程睡眠一小会的代码Thread.sleep(500);
newSingleThreadExecutor
单例的线程池,一直是一个线程在受理业务
newCachedThreadPool
在上面的代码中,我们可以发现的是,对从线程池取的线程睡了0.4s,然而却可以发现创建出了跟多的线程,所以我们可以发现当请求过多的时候会创建线程。
以上是线程池的简单api。
基本原理
首先点进他们的构造方法看看
可以发现,返回的实际上只是一个ThreadPoolExecutor,只是利用构造器传入的不同的参数而已,而且我们也能发现底层是阻塞队列。
同时说明我们也可以通过ThreadPoolExecutor来创建线程池,Executors只是一个创建线程池的工具类,实际上返回的还是ThreadPoolExecutor。
ThreadPoolExecutor
接着继续查看ThreadPoolExecutor
- 找了一张图,解释一下七大参数
然后了解一下每个参数的具体意义
- maximumPool包含corePool,maximumPool表示最多能放的线程数,而corePool表示的就是线程的常驻数
- corePool满了之后,多余的线程就在workQueue等着,就像候客区
- 当阻塞队列满了之后,corePool就开始扩容到maximumPool,如果此时阻塞队列也满了,handle拒绝策略出来了,相当于银行门口立了块牌子,上面写着不办理后面的业务了
- 当多出来的线程空闲下来的时候,即在corePool的基础上扩容的线程,在经过keepAliveTime的时间后就关闭了,重新恢复到corePool个受理窗口
然后简单总结一下线程池的工作流程
首先线程池接收到任务,先判断核心线程数是否满了,没有满继续接客,满了就放到阻塞队列,如果阻塞队列没满,这些任务放在阻塞队列,如果满了,就扩容线程数到最大线程数,如果最大线程数也满了,就是我们的拒绝策略。这就是线程池四大步骤。 接客、放入队列,扩容线程,拒绝策略!
- 创建线程后,开始等待请求
- 当调用了excute()方法添加一个任务的时候,线程池会做出判断
- 如果正在运行的线程数小于corePoolSize,马上创建线程运行该任务
- 如果正在运行的线程数大于或等于corePoolSize,那么将这个任务放入队列
- 如果队列满了,正在运行的线程数小于maximumPoolSize,那么继续创建非核心线程立刻运行该任务
- 如果队列满了,且正在运行的线程数大于或等于maximumPoolSize,那么线程池将会启动饱和拒绝策略
- 当一个线程完成任务后,他会从队列中取下一个任务来执行
- 当一个线程空闲下来且超过keepAliveTime时间时,线程会进行判断
- 如果当前运行的线程数大于corePoolSize,线程会停掉
- 所有线程池的所有任务完成后,他会收缩到corePoolSize大小
实际运用
- 对于newSingleThreadExecutor(); 而言LinkedBlockQueue的长度是Integer.MAX_VALUE,
- 对于newCachedThreadPool()而言,maximumPool的值竟然为Integer.MAX_VALUE!! 两者均会导致OOM异常
自定义线程池
//系统线程数
int tc = Runtime.getRuntime().availableProcessors();
System.out.println(Runtime.getRuntime().availableProcessors());
//自定义线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
threadPool 是我们自定义的线程池,连接过上面的参数的应该都知道,该线程池最大支持的并发量就应该是maximumPool+Queue的大小,即5+3=8,而超过了大小之后就会报错:java.util.concurrent.RejectedExecutionException 拒绝执行异常
以上策略均实现RejectedExecutionHandler接口,具体策略看业务来使用,另外maximumPoolSize的设置一般为CPU核数+1