前言
在企业经常使用到数据导出excel表格,但是在上千上百万条数据量比较多的情况下还使用传统的页面点击导出然后等待下载,数据量比较多导出时间比较长等待时间久,还有可能出现数据大造成数据库压力导出崩溃情况。针对这一情况也有很多的解决方法,为了解决这个情况问题,下面我们通过线程池来实现异步导出excel表格数据。
一、使用线程池好处
线程池(ThreadPoolExecutor)是用于管理线程执行的一个重要工具,它通过重用一组线程来执行任务,从而减少线程的创建与销毁带来的开销,提高系统性能。
- 1、降低资源的消耗:线程可以重复使用,不需要在创建线程和消耗线程上浪费资源;
- 2、提高响应速度:任务到达时,线程可以复用已有的线程,及时响应;
- 3、可管理性:无限制的创建线程会降低系统效率,线程池可以对线程进行管理、监控、调优。
二、线程池详细参数说明
参数名称 | 描述 | 类型 | 默认值 | 备注 |
---|---|---|---|---|
corePoolSize | 线程池的核心线程数。线程池始终保持的最小线程数,即使线程处于空闲状态,也不会被销毁。 | int | 0 | 设定核心线程数。核心线程空闲时如果超过指定时间,则会被回收,除非 allowCoreThreadTimeOut 设置为 true。 |
maximumPoolSize | 线程池的最大线程数。线程池最多能创建的线程数。如果线程池中正在执行的线程数达到核心线程数时,额外任务会在队列中等待,若队列已满,则创建新线程,直到最大线程数。 | int | Integer.MAX_VALUE | maximumPoolSize 是线程池能够允许的最大线程数,超出该数目将采用拒绝策略。 |
keepAliveTime | 线程空闲的最大时间。当线程池中的线程数量超过 corePoolSize 时,空闲线程超过 keepAliveTime 后会被销毁。 |
long | 60L | 此参数作用于线程池中大于 corePoolSize 的线程数。 |
unit | keepAliveTime 的时间单位。用于指定空闲时间的时间单位。 | TimeUnit | TimeUnit.SECONDS | 常见的时间单位有:TimeUnit.SECONDS、TimeUnit.MILLISECONDS、TimeUnit.MINUTES 等。 |
workQueue | 存放任务的阻塞队列。队列用于存储等待执行的任务。若线程池的线程数达到 corePoolSize,新任务会被添加到队列中等待处理。 | BlockingQueue | LinkedBlockingQueue | 常见队列类型包括:LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue 等。队列决定任务的缓存策略。 |
threadFactory | 用于创建新线程的工厂。可以通过 Executors.defaultThreadFactory() 获取一个默认的线程工厂,也可以自定义线程工厂来创建线程。 |
ThreadFactory | Executors.defaultThreadFactory() | 默认的线程工厂会创建一个新线程,并为线程命名(如:pool-1-thread-1)。 |
handler | 拒绝策略。用于处理任务队列已满且线程池中的线程数达到最大值时的任务。可以通过自定义策略来控制任务丢弃、抛出异常或由调用者执行等行为。 | RejectedExecutionHandler | ThreadPoolExecutor.AbortPolicy | 常见拒绝策略包括:AbortPolicy (抛异常),CallerRunsPolicy (由调用者线程执行),DiscardPolicy(直接丢弃任务),DiscardOldestPolicy(丢弃最旧的任务)。 |
参数详细解析
- corePoolSize
- 设定线程池的核心线程数。即使当前没有任务可执行,核心线程数的线程也会保持活动状态。
- 线程池创建时会启动 corePoolSize 数量的线程。
- maximumPoolSize
- 设定线程池的最大线程数。线程池在需要时会创建新的线程,最多创建到 maximumPoolSize。
- 当任务队列满且线程数达到 corePoolSize 时,会尝试创建新线程,直到达到最大线程数。超过这个数目就会执行拒绝策略。
- keepAliveTime
- 设定线程空闲时的最大存活时间。对于非核心线程(线程池中的线程数大于 corePoolSize 时创建的线程),如果超过 keepAliveTime 还没有任务,它们会被销毁。
- 这个时间参数只有在线程池中线程数量大于 corePoolSize 时才会起作用。
- unit
- `keepAliveTime 的单位。单位可以是秒、毫秒、分钟等,通过 TimeUnit 枚举类来表示。
- 常见的单位有 TimeUnit.SECONDS、TimeUnit.MILLISECONDS 和 TimeUnit.MINUTES。
- workQueue
- 任务队列,用于存储等待执行的任务。
- 如果当前的线程池没有足够的线程来执行任务,任务会被添加到队列中等待。如果队列已满,线程池会创建新的线程,直到达到 maximumPoolSize。
- threadFactory
- 线程工厂,用于定制线程的创建。可以用来设置线程的优先级、名称等。
- 默认工厂通过 Executors.defaultThreadFactory() 创建一个新的线程并赋予一个默认的名称,如 pool-1-thread-1。
- handler
- 拒绝策略,处理任务队列已满、线程池已达到最大线程数时的情况。
- 常见的拒绝策略有:
- `AbortPolicy(默认):抛出 RejectedExecutionException 异常。
- `CallerRunsPolicy:由调用者线程执行该任务。
- `DiscardPolicy:丢弃当前任务。
- `DiscardOldestPolicy:丢弃队列中最旧的任务,并尝试执行当前任务。
import java.util.concurrent.*;
public class ThreadPoolExecutorExample {
public static void main(String[] args) {
int corePoolSize = 2;
int maximumPoolSize = 4;
long keepAliveTime = 60L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(10);
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue
);
// 创建并提交任务
for (int i = 0; i < 20; i++) {
executor.submit(new Task(i));
}
// 优雅关闭线程池
executor.shutdown();
}
static class Task implements Runnable {
private final int taskId;
public Task(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Executing task " + taskId + " by " + Thread.currentThread().getName());
}
}
}
三、使用单线程异步导出excel代码分析
1、异步导出公共调用方法
- 1.这个方法是通用的对外方法(synExportCommom),sourceType是知道导出数据的模板类型,queryDTO是接口的请求参数。
- 2.uploadObsAttachmentSyncService这个Service层主要存储导出的excel文件上传到远程服务器的路径。
- executor这个线程是父级线程也是主线程。
private final static ExecutorService executor = Executors.newFixedThreadPool(4);
@Override
public void synExportCommom(HttpServletResponse response, String sourceType, QeryDTO queryDTO) {
String employeeNum = "0110210"
long count = uploadObsAttachmentSyncService.countHandling(employeeNum, sourceType);
// 先校验是否有任务正则执行中