You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java多线程结合POI同时读写同一Excel(HSSF/XSSF)工作表的最优方案?

好的,针对你这个Excel多线程处理的需求,得先明确一个关键前提:Apache POI的Workbook、Sheet、Row这些核心对象都不是线程安全的——直接让多个线程操作同一个Sheet实例大概率会导致数据混乱、抛出异常,所以不能直接在多线程里读写Excel对象。

最优的方案应该是「数据读取→多线程并行处理数据→单线程统一写入」的流程,把CPU密集的逻辑处理部分并行化,而IO/Excel操作部分保持单线程,既保证安全又提升效率。下面是具体的实现步骤和代码示例:

1. 定义数据实体类

先封装每条记录的信息,包含行号(方便后续写入对应位置)、Name、Age和处理后的Remarks:

public class PersonRecord {
    private int rowIndex; // POI中行索引从0开始,若表头是第1行,数据行从索引1开始
    private String name;
    private int age;
    private String remarks;

    // 生成构造函数、getter、setter方法
    public PersonRecord(int rowIndex, String name, int age) {
        this.rowIndex = rowIndex;
        this.name = name;
        this.age = age;
    }

    // getter和setter省略,按需生成
}

2. 单线程读取Excel数据

先把所有需要处理的Name和Age数据读取到内存中,避免多线程操作POI对象:

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class ExcelMultiThreadProcessor {
    private static final String EXCEL_PATH = "File.xlsx";
    // 假设列索引:Name在第1列(0)、Age在第2列(1)、Remarks在第3列(2)
    private static final int NAME_COL = 0;
    private static final int AGE_COL = 1;
    private static final int REMARKS_COL = 2;

    // 单线程读取Excel数据
    private static List<PersonRecord> readExcelData() throws IOException {
        List<PersonRecord> records = new ArrayList<>();
        try (FileInputStream fis = new FileInputStream(EXCEL_PATH);
             Workbook workbook = new XSSFWorkbook(fis)) {
            Sheet sheet = workbook.getSheetAt(0); // 取第一个工作表
            // 跳过表头,从第2行开始读取(索引1)
            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                if (row == null) continue;

                Cell nameCell = row.getCell(NAME_COL);
                Cell ageCell = row.getCell(AGE_COL);

                String name = nameCell != null ? nameCell.getStringCellValue() : "";
                int age = ageCell != null ? (int) ageCell.getNumericCellValue() : 0;

                records.add(new PersonRecord(i, name, age));
            }
        }
        return records;
    }

3. 编写并行处理的任务

把数据处理逻辑封装成Runnable任务,让多线程只处理内存中的数据,不碰POI对象:

// 模拟你的业务处理逻辑,替换成实际代码
    private static String generateRemarks(String name, int age) {
        // 示例逻辑:根据姓名和年龄生成备注
        return age >= 18 ? name + "是成年人" : name + "是未成年人";
    }

    // 处理子数据集的任务类
    static class RecordProcessTask implements Runnable {
        private final List<PersonRecord> subRecords;

        public RecordProcessTask(List<PersonRecord> subRecords) {
            this.subRecords = subRecords;
        }

        @Override
        public void run() {
            for (PersonRecord record : subRecords) {
                String remarks = generateRemarks(record.getName(), record.getAge());
                record.setRemarks(remarks);
            }
        }
    }

4. 主流程:拆分数据+并行处理+写入结果

public static void main(String[] args) throws IOException, InterruptedException {
        // 1. 单线程读取所有数据
        List<PersonRecord> allRecords = readExcelData();
        int totalCount = allRecords.size();

        // 2. 拆分数据为两组:前500条和剩余部分(兼容非1000条的情况)
        int splitPoint = Math.min(500, totalCount);
        List<PersonRecord> group1 = allRecords.subList(0, splitPoint);
        List<PersonRecord> group2 = allRecords.subList(splitPoint, totalCount);

        // 3. 启动两个线程并行处理
        Thread thread1 = new Thread(new RecordProcessTask(group1));
        Thread thread2 = new Thread(new RecordProcessTask(group2));
        thread1.start();
        thread2.start();
        // 等待两个线程处理完成
        thread1.join();
        thread2.join();

        // 4. 单线程写入处理后的Remarks列
        try (FileInputStream fis = new FileInputStream(EXCEL_PATH);
             Workbook workbook = new XSSFWorkbook(fis)) {
            Sheet sheet = workbook.getSheetAt(0);
            for (PersonRecord record : allRecords) {
                Row row = sheet.getRow(record.getRowIndex());
                if (row == null) {
                    row = sheet.createRow(record.getRowIndex());
                }
                Cell remarksCell = row.createCell(REMARKS_COL);
                remarksCell.setCellValue(record.getRemarks());
            }
            // 写入到新文件(避免覆盖原文件,验证后可替换)
            try (FileOutputStream fos = new FileOutputStream("Processed_File.xlsx")) {
                workbook.write(fos);
            }
            System.out.println("Excel处理完成!");
        }
    }
}

关键注意事项

  • POI线程安全问题:绝对不要在多线程中共享Workbook、Sheet、Row实例,我们的方案中读写Excel都是单线程,多线程只处理内存数据,完全规避了并发风险。
  • 数据边界处理:用Math.min处理拆分点,避免记录数不足1000时出现索引越界。
  • 文件写入安全:建议写入新文件,验证无误后再替换原文件,防止数据丢失。
  • 线程池扩展:如果后续需要更多线程,推荐使用ExecutorService(比如Executors.newFixedThreadPool(2)),比手动创建线程更易管理。

内容的提问来源于stack exchange,提问作者Arun Patil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 15:32:35