目录
前言
线程池重点内容:三大方法、7大参数、拒绝策略、优化配置。
程序运行的本质:占用系统资源,CPU/磁盘网络进行使用!我们希望可以高效的使用!池化技术就是演进出来的。
- 简单的说,池化技术就是:提前准备一些资源、以供使用!
- 线程池、连接池、内存池、对象池…这些东西都是池化技术。
- 线程的创建和销毁,数据库的连接和断开都十分浪费资源。
只要是“池”,就会设计到两个常量:minSize、maxSize,这些就是为了弹性访问,保证系统运行的效率。
12. 线程池
12.1 线程池概述
线程池 thread pool
一种线程使用模式。线程过多会带来调度开销, 进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
线程池的优势
线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量, 超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
线程池的主要特点为
- 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
- 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
12.2 线程池架构
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors(工具类), ExecutorService,ThreadPoolExecutor 这几个类
12.3 线程池使用方式
数组有工具类Arrays,集合有工具类Collections,线程池同样有工具类Executors。利用线程池工具类Executors来创建线程池。
线程池 Executors原生三大方法:
// 一池N线程 固定线程池大小为N
1、ExecutorService threadpool = Executors.newFixedThreadPool(5);
//可弹性伸缩的线程池,遇强则强
2、ExecutorService threadpool = Executors.newCachedThreadPool();
// 一池一线程
3、ExecutorService threadpool = Executors.newSingleThreadExecutor();
1、创建线程池 : ExecutorService threadpool = …
2、线程池执行线程: threadpool.execute();
3、关闭线程池 : threadpool.shutdown()
三个方法创建线程池案例如下
public static void cachedThreadPool() {
/**1、创建线程池*/
ExecutorService threadpool = Executors.newFixedThreadPool(5);
/可弹性伸缩的线程池,遇强则强
//ExecutorService threadpool = Executors.newCachedThreadPool();
// 一池一线程
//ExecutorService threadpool = Executors.newSingleThreadExecutor();
//线程池要关闭,一般关闭我们放在finally中执行
try {
for (int i = 0; i < 10; i++) {
/** 2、线程池执行线程*/
executorService.execute(() -> System.out.println(Thread.currentThread().getName() + ":running"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
/**3、关闭线程池 */
executorService.shutdown();
}
}
12.4 线程池的底层原理
Executors.newFixedThreadPool(int number) 、 Executors.newCachedThreadPool()
、Executors.newSingleThreadExecutor()线程池的三大方法最终调用的都是ThreadPoolExecutor,一共有7个参数
12.5 线程池的七个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler ) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
• corePoolSize 线程池的核心线程数
• maximumPoolSize 能容纳的最大线程数
• keepAliveTime 空闲线程存活时间
• unit 存活的时间单位
• workQueue 存放提交但未执行任务的队列
• threadFactory 创建线程的工厂类
• handler 等待队列满后的拒绝策略
12.6 线程底层工作流程
1、队列满了,就会触发最大线程池,否则永远都只是corePoolSize个线程在运行,所以,队列大小一定要根据业务情况进行设置;
2、当请求线程超过线程池(maximumPoolSize + workQueue),就会触发拒绝策略,至于怎么拒绝,与拒绝策略RejectedExecutionHandler有关。
12.7 四种拒绝策略
拒绝策略的触发:当提交的任务数大于(workQueue.size() + maximumPoolSize )
拒绝策略说明:
- AbortPolicy (默认的:队列满了,就丢弃任务抛出异常!);
- CallerRunsPolicy(哪来的回哪去? 谁叫你来的,你就去哪里处理);
- DiscardOldestPolicy (尝试将最早进入队列的任务删除,尝试加入新任务);
- DiscardPolicy (队列满了任务也会丢弃,不抛出异常)。
12.8 自定义线程池
为什么需要自定义线程池。
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
threadPoolExecutor();
}
public static void threadPoolExecutor(){
ExecutorService threadPool = new ThreadPoolExecutor(
2, // 核心池子的大小
5, // 线程池最大大小5
2L, // 空闲线程的保留时间
TimeUnit.SECONDS, // 超时回收空闲的线程
new LinkedBlockingDeque<>(3), // 根据业务设置队列大小,队列大小一定要设置
Executors.defaultThreadFactory(), // 不用变
new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
);
try {
// 队列 RejectedExecutionException 拒绝策略
for (int i = 1; i <= 10; i++) {
// 默认在处理
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" running....");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}