springboot 分片上传文件 - postgres(BLOB存储)

  • 方案一(推荐)

​ 接收完整文件,后端自动分片并存储(多线程 大文件)

	/**
     * 接收完整文件,后端自动分片并存储(多线程 大文件)
     * @param file
     * @return
     * @throws Exception
     */
    public String uploadChunkFile(MultipartFile file) throws Exception {
        String uploadId = UUID.randomUUID().toString();
        long fileSize = file.getSize();
        long totalChunks = (long) Math.ceil((double) fileSize / CHUNK_SIZE);
        if (totalChunks <= 0) {
            return "文件大小异常,无法分片";
        }

        // 1. 创建临时目录存储分片(大文件避免内存溢出)
        File tempDir = Files.createTempDirectory("file-chunk-").toFile();
        //设置JVM退出时自动删除该目录
        tempDir.deleteOnExit();

        try (InputStream inputStream = file.getInputStream()) {
            byte[] buffer = new byte[(int) CHUNK_SIZE];
            int bytesRead;
            int chunkIndex = 0;

            // 2. 先将所有分片写入临时文件(流式处理,不占大量内存)
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                File chunkFile = new File(tempDir, uploadId + "-" + chunkIndex);
                try (FileOutputStream fos = new FileOutputStream(chunkFile)) {
                    fos.write(buffer, 0, bytesRead); // 只写实际读取的字节
                }
                chunkIndex++;
            }

            // 3. 一次性提交所有分片任务,使用工具类等待完成
            ThreadPoolUtils.getNewInstance().submitBatchTasks((int) totalChunks, taskIndex -> {
                try {
                    // 读取临时分片文件(每个任务只加载自己的分片数据)
                    File chunkFile = new File(tempDir, uploadId + "-" + taskIndex);
                    byte[] chunkData = Files.readAllBytes(chunkFile.toPath());

                    // 存储到数据库
                    FileUploadEntity entity = new FileUploadEntity();
                    entity.setId(IdGenerator.nextId());
                    entity.setUploadId(uploadId);
                    entity.setChunkSize((long) chunkData.length);
                    entity.setChunkNum(totalChunks);
                    entity.setChunkFile(chunkData);
                    entity.setChunkIndex(taskIndex);
                    fileUploadMapper.insertFile(entity);

                } catch (IOException e) {
                    throw new RuntimeException("分片" + taskIndex + "存储失败", e);
                }
            });

        } catch (Exception e) {
            log.error("文件分片上传失败", e);
            throw new RuntimeException("文件分片上传失败");
        } finally {
            // 4. 清理临时文件
            deleteDir(tempDir);
        }

        return "文件已成功分片存储,uploadId: " + uploadId;
    }

    // 递归删除临时目录
    private boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            File[] children = dir.listFiles();
            if (children != null) {
                for (File child : children) {
                    deleteDir(child);
                }
            }
        }
        return dir.delete();
    }
  • 方案二

​ 接收完整文件,后端自动分片并存储(多线程 小文件)。。。 大文件可能会内存溢出

/**
 * 接收完整文件,后端自动分片并使用多线程存储 (多线程 小文件)
 * @param file
 * @return
 * @throws IOException, InterruptedException
 */
public String uploadChunkFile(MultipartFile file) throws IOException, InterruptedException {
    // 生成唯一上传ID,用于标识同一文件的所有分片
    String uploadId = UUID.randomUUID().toString();
    String fileName = file.getOriginalFilename();
    long fileSize = file.getSize();

    // 计算总分片数
    long totalChunks = (long) Math.ceil((double) fileSize / CHUNK_SIZE);
    if (totalChunks <= 0) {
        return "文件大小异常,无法分片";
    }

    // 读取文件所有分片数据到内存(小文件适用,大文件建议使用磁盘临时文件)
    List<byte[]> chunkDataList = new ArrayList<>();
    try (InputStream inputStream = file.getInputStream()) {
        byte[] buffer = new byte[(int) CHUNK_SIZE];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            byte[] chunkData = new byte[bytesRead];
            System.arraycopy(buffer, 0, chunkData, 0, bytesRead);
            chunkDataList.add(chunkData);
        }
    }

    // 获取线程池工具类实例
    ThreadPoolUtils threadPool = ThreadPoolUtils.getNewInstance();
    // 提交批量分片任务并等待完成
    threadPool.submitBatchTasks((int) totalChunks, chunkIndex -> {
        byte[] currentChunkData = chunkDataList.get(chunkIndex);
        long currentChunkSize = currentChunkData.length;
        
        // 存储分片数据到数据库
        FileUploadEntity fileUpload = new FileUploadEntity();
        fileUpload.setId(IdGenerator.nextId());
        fileUpload.setUploadId(uploadId);
        fileUpload.setChunkSize(currentChunkSize);
        fileUpload.setChunkNum(totalChunks);
        fileUpload.setChunkFile(currentChunkData);
        fileUpload.setChunkIndex(chunkIndex);
        fileUploadMapper.insertFile(fileUpload);
    });

    return "文件已成功分片存储,uploadId: " + uploadId;
}
  • 方案三

​ 接收完整文件,后端自动分片并存储 (单线程) 。。。。上传大文件时间太久

    /**
     * 接收完整文件,后端自动分片并存储
     * @param file
     * @return
     * @throws IOException
     */
    public String uploadChunkFileBackup(MultipartFile file) throws IOException {
        // 生成唯一上传ID,用于标识同一文件的所有分片
        String uploadId = UUID.randomUUID().toString();
        String fileName = file.getOriginalFilename();
        long fileSize = file.getSize();

        // 计算总分片数
        long totalChunks = (long) Math.ceil((double) fileSize / CHUNK_SIZE);

        List<FileUploadEntity> list = new ArrayList<>();
        try (InputStream inputStream = file.getInputStream()) {
            byte[] buffer = new byte[(int) CHUNK_SIZE];
            int bytesRead;
            int chunkIndex = 0;

            // 循环读取文件并分片
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                // 处理最后一个可能小于标准分片大小的分片
                byte[] chunkData = new byte[bytesRead];
                System.arraycopy(buffer, 0, chunkData, 0, bytesRead);
                // 获取分片实际大小(字节数)
                long chunkActualSize = bytesRead;  // 这就是当前分片的实际大小

                // 存储当前分片
//                saveChunk(uploadId, chunkIndex, totalChunks, chunkData, fileSize, fileName);
                // 存储当前分片
                FileUploadEntity fileUpload = new FileUploadEntity();
                fileUpload.setId(IdGenerator.nextId());
                fileUpload.setUploadId(uploadId);
                fileUpload.setChunkSize(chunkActualSize);
                fileUpload.setChunkNum(totalChunks);
                fileUpload.setChunkFile(chunkData);
                fileUpload.setChunkIndex(chunkIndex);
                fileUploadMapper.insertFile(fileUpload);
//                list.add(fileUpload);

                chunkIndex++;
            }
        }

        //批量添加
//        int batchSize = 500;
//        for (int i = 0; i < list.size(); i += batchSize) {
//            int end = Math.min(i + batchSize, list.size());
//            List<FileUploadEntity> subList = list.subList(i, end);
//            fileUploadMapper.batchInsert(subList);
//        }

        return "文件已成功分片存储,uploadId: " + uploadId;
    }
  • 方案四

    接收完整文件,后端自动分片并使用 (多线程)线程池未封装

/**
     * 接收完整文件,后端自动分片并使用 (多线程)线程池未封装
     * @param file
     * @return
     * @throws IOException, InterruptedException
     */
//    @Override
    public String uploadChunkFile(MultipartFile file) throws IOException, InterruptedException {
        // 生成唯一上传ID,用于标识同一文件的所有分片
        String uploadId = UUID.randomUUID().toString();
        String fileName = file.getOriginalFilename();
        long fileSize = file.getSize();

        // 计算总分片数
        long totalChunks = (long) Math.ceil((double) fileSize / CHUNK_SIZE);

        // 创建线程池,核心线程数可根据服务器配置调整
        // 通常设置为CPU核心数 * 2 + 1
        int corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
        ExecutorService executorService = Executors.newFixedThreadPool(corePoolSize);

        // 使用CountDownLatch等待所有线程完成
        CountDownLatch countDownLatch = new CountDownLatch((int) totalChunks);

        try (InputStream inputStream = file.getInputStream()) {
            byte[] buffer = new byte[(int) CHUNK_SIZE];
            int bytesRead;
            int chunkIndex = 0;

            // 循环读取文件并分片
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                // 处理最后一个可能小于标准分片大小的分片
                byte[] chunkData = new byte[bytesRead];
                System.arraycopy(buffer, 0, chunkData, 0, bytesRead);
                long chunkActualSize = bytesRead;

                // 捕获当前变量的快照,避免线程安全问题
                final int currentChunkIndex = chunkIndex;
                final byte[] currentChunkData = chunkData;
                final long currentChunkSize = chunkActualSize;

                // 提交分片存储任务到线程池
                executorService.submit(() -> {
                    try {
                        FileUploadEntity fileUpload = new FileUploadEntity();
                        fileUpload.setId(IdGenerator.nextId());
                        fileUpload.setUploadId(uploadId);
                        fileUpload.setChunkSize(currentChunkSize);
                        fileUpload.setChunkNum(totalChunks);
                        fileUpload.setChunkFile(currentChunkData);
                        fileUpload.setChunkIndex(currentChunkIndex);
                        fileUploadMapper.insertFile(fileUpload);
                    } finally {
                        // 无论是否发生异常,都减少计数器
                        countDownLatch.countDown();
                    }
                });

                chunkIndex++;
            }

            // 等待所有分片处理完成
            countDownLatch.await();
        } finally {
            // 关闭线程池
            executorService.shutdown();
        }

        return "文件已成功分片存储,uploadId: " + uploadId;
    }
  • 方案五

    大对象(Large Object)方案

 /**
     * 大对象(Large Object)方案
     *
     * PostgreSQL 的大对象(Large Object)机制要求:
     * 二进制数据通过LargeObjectManager写入,返回一个OID(数字类型的对象 ID)
     * 表中只存储这个OID,而不是直接存储二进制数据
     * 读取时通过OID从大对象管理器中获取数据
     * @param file
     * @return
     */
    @Override
    public String uploadLargeObjectFile(MultipartFile file) {
        if (file.isEmpty()) {
            return "请选择文件";
        }
        try {
            long fileSize = file.getSize();
            String fileName = file.getOriginalFilename();
            long largeObjectId = postgresLargeObjectUtil.createLargeObject(file.getInputStream());
            FileUploadEntity fileUpload = new FileUploadEntity();
            fileUpload.setId(IdGenerator.nextId());
            fileUpload.setUploadId(String.valueOf(largeObjectId));
            fileUpload.setChunkSize(fileSize);
            fileUpload.setChunkNum(fileSize);
            fileUpload.setChunkFile(null);
            fileUpload.setChunkIndex(2);
            fileUploadMapper.insertLargeObjectFile(fileUpload);
            return "大文件上传成功!文件名:" + fileName + ",大小:" + fileSize + "字节";
        }catch (Exception e) {
            log.error("上传大文件失败:{}", e);
            return "上传失败:" + e.getMessage();
        }
    }

	//下载
    @Override
    public void downloadFile(Long fileId, HttpServletResponse response) {
        FileUploadEntity fileEntity = fileUploadMapper.getFileById(fileId);
        long oid = Long.valueOf( fileEntity.getUploadId());
        try {
            response.reset();
            response.setContentType("application/octet-stream");
            String filename = "fileName.zip";
            response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
            ServletOutputStream outputStream = response.getOutputStream();
            postgresLargeObjectUtil.readLargeObject(oid, outputStream);
        }catch (Exception e) {
            log.error("下载文件失败:{}", e);
        }
    }
  • 方案六

    文件字节上传

/**
     * 文件字节上传
     * @param file
     * @return
     */
    @Override
    public String uploadFileByte(MultipartFile file) {
        if (file.isEmpty()) {
            return "请选择文件";
        }

        try {
            // 获取文件信息
            String fileName = file.getOriginalFilename();
            long fileSize = file.getSize();
            byte[] fileData = file.getBytes(); // 小文件直接获取字节数组

            // 执行插入(大文件建议用流:file.getInputStream())
            String sql = "INSERT INTO system_upload_test (id, upload_id, chunk_size, chunk_num, chunk_file, chunk_index) VALUES (?, ?, ?, ?, ?, ?)";
            jdbcTemplate.update(sql,
                    111L,
                    "2222",
                    222L,
                    3L,
                    fileData,
                    33L
            );
            return "文件上传成功!";
        } catch (Exception e) {
            e.printStackTrace();
            return "文件上传失败:" + e.getMessage();
        }
    }


  	// 大文件用流:file.getInputStream()
    public String uploadBigFile(MultipartFile file) throws Exception {
        // 1. 定义 SQL(注意:字段顺序和占位符对应)
        String sql = "INSERT INTO user_qgcgk_app.system_upload_test " +
                "(id, upload_id, chunk_size, chunk_num, chunk_file, chunk_index) " +
                "VALUES (?, ?, ?, ?, ?, ?)";

        // 2. 准备参数(确保 InputStream 未关闭)
        Long id = 1795166209435262976L;
        String uploadId = "3333";
        Long chunkSize = 7068L;
        Long chunkNum = 7068L;
        InputStream chunkInputStream = file.getInputStream(); // 你的 InputStream(如 FileInputStream、ServletInputStream)
        Integer chunkIndex = 2;

        try {
            // 3. 执行 SQL:通过 PreparedStatementSetter 手动绑定参数
            jdbcTemplate.update(sql, new PreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps) throws SQLException {
                    // 绑定非流参数(按顺序,类型匹配)
                    ps.setLong(1, id);                  // 第1个参数:id(Long)
                    ps.setString(2, uploadId);          // 第2个参数:upload_id(String)
                    ps.setLong(3, chunkSize);           // 第3个参数:chunk_size(Long)
                    ps.setLong(4, chunkNum);            // 第4个参数:chunk_num(Long)
                    // 关键:绑定 InputStream 到 bytea 字段(第5个参数)
                    // 第三个参数传 -1 表示“未知流长度”,PostgreSQL 支持此模式
                    ps.setBinaryStream(5, chunkInputStream, file.getSize());
                    ps.setInt(6, chunkIndex);           // 第6个参数:chunk_index(Int)
                }
            });
        } finally {
            // 4. 执行完成后关闭流,释放资源
            if (chunkInputStream != null) {
                chunkInputStream.close();
            }
        }
        return "上传成功!";
    }
  • 方案七
    无临时文件+多线(减少IO操作)
 	/**
     *   无临时文件+多线程+批量插入的分片上传
     */
    public String uploadChunkFile(MultipartFile file) throws Exception {
        // 生成唯一上传ID
        String uploadId = UUID.randomUUID().toString();
        long fileSize = file.getSize();
        long totalChunks = (long) Math.ceil((double) fileSize / CHUNK_SIZE);

        if (totalChunks <= 0) {
            return "文件大小异常,无法分片";
        }

        try (InputStream inputStream = file.getInputStream()) {
            byte[] buffer = new byte[(int) CHUNK_SIZE];
            int bytesRead;
            int chunkIndex = 0;
            // 批量插入缓冲区(每10个分片一批)
            List<FileUploadEntity> batchList = new ArrayList<>(10);
            // 计数器:等待所有批量任务完成
            CountDownLatch latch = new CountDownLatch((int) Math.ceil((double) totalChunks / 10));

            // 流式读取并处理分片
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                // 复制当前分片数据(避免buffer被覆盖)
                byte[] chunkData = Arrays.copyOfRange(buffer, 0, bytesRead);

                // 创建分片实体
                FileUploadEntity entity = new FileUploadEntity();
                entity.setId(IdGenerator.nextId());
                entity.setUploadId(uploadId);
                entity.setChunkSize((long) chunkData.length);
                entity.setChunkNum(totalChunks);
                entity.setChunkFile(chunkData);
                entity.setChunkIndex(chunkIndex);

                batchList.add(entity);
                chunkIndex++;

                // 批量条件:满10个分片或最后一个分片
                if (batchList.size() >= 10 || chunkIndex == totalChunks) {
                    // 复制当前批次(避免线程安全问题)
                    List<FileUploadEntity> currentBatch = new ArrayList<>(batchList);

                    // 提交批量插入任务
                    ThreadPoolUtils.getNewInstance().executor(() -> {
                        try {
                            fileUploadMapper.batchInsert(currentBatch);
                        } finally {
                            latch.countDown(); // 任务完成,计数器减1
                        }
                    });

                    batchList.clear(); // 清空缓冲区
                }
            }

            // 等待所有批量任务完成(最多等待5分钟)
            boolean allCompleted = latch.await(5, java.util.concurrent.TimeUnit.MINUTES);
            if (!allCompleted) {
                throw new BusinessException("文件分片上传超时,请重试");
            }

        } catch (Exception e) {
            log.error("文件分片上传失败,uploadId:{}", uploadId, e);
            // 失败时清理已上传的分片(可选)
//            fileUploadMapper.deleteByUploadId(uploadId);
            throw new BusinessException("文件分片上传失败:" + e.getMessage());
        }

        return "文件已成功分片存储,uploadId: " + uploadId;
    }

 	/**
     *   无临时文件+多线程+单条插入的分片上传
     */
    public String uploadChunkFile(MultipartFile file) throws Exception {
        String uploadId = UUID.randomUUID().toString();
        long fileSize = file.getSize();
        long totalChunks = (long) Math.ceil((double) fileSize / CHUNK_SIZE);
        if (totalChunks <= 0) {
            return "文件大小异常,无法分片";
        }

        try (InputStream inputStream = file.getInputStream()) {
            byte[] buffer = new byte[(int) CHUNK_SIZE];
            int bytesRead;
            int chunkIndex = 0;
            // 用于等待所有分片完成
            CountDownLatch latch = new CountDownLatch((int) totalChunks);

            // 边读取边提交分片任务,无需临时文件
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                // 复制当前分片数据(避免buffer被下一次read覆盖)
                byte[] chunkData = Arrays.copyOfRange(buffer, 0, bytesRead);
                final int currentIndex = chunkIndex;

                // 提交异步任务
                ThreadPoolUtils.getNewInstance().executor(() -> {
                    try {
                        // 直接用内存中的分片数据写入数据库
                        FileUploadEntity entity = new FileUploadEntity();
                        entity.setId(IdGenerator.nextId());
                        entity.setUploadId(uploadId);
                        entity.setChunkSize((long) chunkData.length);
                        entity.setChunkNum(totalChunks);
                        entity.setChunkFile(chunkData);
                        entity.setChunkIndex(currentIndex);
                        fileUploadMapper.insertFile(entity);
                    } catch (Exception e) {
                        throw new RuntimeException("分片" + currentIndex + "存储失败", e);
                    } finally {
                        latch.countDown();
                    }
                });

                chunkIndex++;
            }

            // 等待所有分片完成
            latch.await();

        } catch (Exception e) {
            log.error("文件分片上传失败", e);
            throw new BusinessException("文件分片上传失败");
        }

        return "文件已成功分片存储,uploadId: " + uploadId;
    }




- 工具类

  PostgreSQL大对象工具类

```java
import lombok.extern.slf4j.Slf4j;
import org.postgresql.largeobject.LargeObject;
import org.postgresql.largeobject.LargeObjectManager;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * PostgreSQL大对象工具类
 * @author: zrf
 * @date: 2025/08/25 16:09
 */
@Slf4j
@Component
public class PostgresLargeObjectUtil {

    private final JdbcTemplate jdbcTemplate;

    public PostgresLargeObjectUtil(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 从输入流创建大对象并返回OID
     */
    @Transactional
    public long createLargeObject(InputStream inputStream) throws SQLException {
        // 获取数据库连接并关闭自动提交
        Connection connection = jdbcTemplate.getDataSource().getConnection();
        connection.setAutoCommit(false);

        try {
            // 获取PostgreSQL大对象管理器
            LargeObjectManager lobjManager = connection.unwrap(org.postgresql.PGConnection.class)
                    .getLargeObjectAPI();

            // 创建大对象,返回OID
            long oid = lobjManager.createLO(LargeObjectManager.READ | LargeObjectManager.WRITE);

            // 打开大对象并写入数据
            try (LargeObject largeObject = lobjManager.open(oid, LargeObjectManager.WRITE)) {
                OutputStream outputStream = largeObject.getOutputStream();
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }

            connection.commit();
            return oid;
        } catch (Exception e) {
            connection.rollback();
            throw new SQLException("创建大对象失败", e);
        } finally {
            connection.close();
        }
    }

    /**
     * 根据OID读取大对象内容到输出流
     */
    public void readLargeObject(long oid, OutputStream outputStream) throws Exception {
        Connection connection = jdbcTemplate.getDataSource().getConnection();
        connection.setAutoCommit(false);

        try {
            LargeObjectManager lobjManager = connection.unwrap(org.postgresql.PGConnection.class)
                    .getLargeObjectAPI();

            try (LargeObject largeObject = lobjManager.open(oid, LargeObjectManager.READ)) {
                InputStream inputStream = largeObject.getInputStream();
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }
            connection.commit();
        } catch (Exception e) {
            log.error("读取大对象失败", e);
        } finally {
            connection.close();
        }
    }

    /**
     * 删除大对象(释放磁盘空间)
     */
    @Transactional
    public void deleteLargeObject(long oid) throws SQLException {
        Connection connection = jdbcTemplate.getDataSource().getConnection();
        connection.setAutoCommit(false);
        
        try {
            LargeObjectManager lobjManager = connection.unwrap(org.postgresql.PGConnection.class)
                    .getLargeObjectAPI();
            lobjManager.delete(oid);
            connection.commit();
        } catch (Exception e) {
            connection.rollback();
            throw new SQLException("删除大对象失败", e);
        } finally {
            connection.close();
        }
    }
}

线程池工具类


import java.util.List;
import java.util.concurrent.*;
import java.util.function.Consumer;

/**
 * @Author:zrf
 * @Date:2023/8/14 10:05
 * @description:线程池工具类
 */
public class ThreadPoolUtils {

    /**
     * 系统可用计算资源
     */
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

    /**
     * 核心线程数
     */
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));

    /**
     * 最大线程数
     */
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

    /**
     * 空闲线程存活时间
     */
    private static final int KEEP_ALIVE_SECONDS = 30;

    /**
     * 工作队列
     */
    private static final BlockingQueue<Runnable> POOL_WORK_QUEUE = new LinkedBlockingQueue<>(128);

    /**
     * 工厂模式
     */
    private static final MyThreadFactory MY_THREAD_FACTORY = new MyThreadFactory();

    /**
     * 饱和策略
     */
    private static final ThreadRejectedExecutionHandler THREAD_REJECTED_EXECUTION_HANDLER = new ThreadRejectedExecutionHandler.CallerRunsPolicy();

    /**
     * 线程池对象
     */
    private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR;

    /**
     * 声明式定义线程池工具类对象静态变量,在所有线程中同步
     */
    private static volatile ThreadPoolUtils threadPoolUtils = null;


    /**
     * 初始化线程池静态代码块
     */
    static {
        THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
                //核心线程数
                CORE_POOL_SIZE,
                //最大线程数
                MAXIMUM_POOL_SIZE,
                //空闲线程执行时间
                KEEP_ALIVE_SECONDS,
                //空闲线程执行时间单位
                TimeUnit.SECONDS,
                //工作队列(或阻塞队列)
                POOL_WORK_QUEUE,
                //工厂模式
                MY_THREAD_FACTORY,
                //饱和策略
                THREAD_REJECTED_EXECUTION_HANDLER
                );
    }


    /**
     * 线程池工具类空参构造方法
     */
    private ThreadPoolUtils() {}

    /**
     * 获取线程池工具类实例
     * @return
     */
    public static ThreadPoolUtils getNewInstance(){
        if (threadPoolUtils == null) {
            synchronized (ThreadPoolUtils.class) {
                if (threadPoolUtils == null) {
                    threadPoolUtils = new ThreadPoolUtils();
                }
            }
        }
        return threadPoolUtils;
    }


    /**
     * 执行线程任务
     * @param runnable 任务线程
     */
    public void executor(Runnable runnable) {
        THREAD_POOL_EXECUTOR.execute(runnable);
    }

    /**
     * 执行线程任务-有返回值
     * @param callable 任务线程
     */
    public <T> Future<T> submit(Callable<T> callable) {
        return THREAD_POOL_EXECUTOR.submit(callable);
    }

    /**
     * 提交批量任务并等待所有任务完成
     * @param totalTasks 总任务数量
     * @param taskConsumer 任务消费者(接收任务索引,处理具体任务逻辑)
     * @throws InterruptedException 等待被中断时抛出
     */
    public void submitBatchTasks(int totalTasks, Consumer<Integer> taskConsumer) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(totalTasks);
        for (int i = 0; i < totalTasks; i++) {
            final int taskIndex = i;
            // 使用现有线程池提交任务
            THREAD_POOL_EXECUTOR.submit(() -> {
                try {
                    taskConsumer.accept(taskIndex); // 执行具体任务逻辑
                } finally {
                    countDownLatch.countDown(); // 任务完成,计数器减1
                }
            });
        }
        countDownLatch.await(); // 等待所有任务完成
    }

    /**
     * 获取线程池状态
     * @return 返回线程池状态
     */
    public boolean isShutDown(){
        return THREAD_POOL_EXECUTOR.isShutdown();
    }

    /**
     * 停止正在执行的线程任务
     * @return 返回等待执行的任务列表
     */
    public List<Runnable> shutDownNow(){
        return THREAD_POOL_EXECUTOR.shutdownNow();
    }

    /**
     * 关闭线程池
     */
    public void showDown(){
        THREAD_POOL_EXECUTOR.shutdown();
    }


    /**
     * 关闭线程池后判断所有任务是否都已完成
     * @return
     */
    public boolean isTerminated(){
        return THREAD_POOL_EXECUTOR.isTerminated();
    }
}

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐