多线程下用Apache POI调用createRow()仍遇ConcurrentModificationException
从你的异常栈和代码片段来看,问题的核心是你用的同步锁根本没起到互斥作用,导致多个线程仍在同时修改XSSFSheet对象,触发了并发修改异常。
为什么synchronized(this)无效?
你的每个线程都是RootQueryThread的独立实例,每个实例的this是不同的对象。当多个线程进入synchronized(this)代码块时,它们各自持有自己的锁,完全不会互相阻塞,多个线程还是能同时操作同一个sheet对象——这正是异常的根源。
正确的解决方案
要让所有线程共享同一个锁对象,确保同一时间只有一个线程操作sheet,这里有几种可行的方案:
方案1:以sheet对象作为锁(推荐)
因为所有线程操作的是同一个sheet实例,直接用它作为锁对象,能精准控制对sheet的互斥访问:
@Override public void run() { ... // add row to excel sheet synchronized(sheet) { // 替换为sheet作为锁对象 Row row = sheet.createRow(querySequence+1); row.createCell(0).setCellValue(rqResult.getRQName()); // 其他单元格操作 } }
注意:必须确保所有线程引用的是同一个sheet实例,这个锁才能生效。
方案2:使用类级别的锁
如果sheet实例不方便直接传递给线程,也可以用RootQueryThread.class作为锁——类对象是全局唯一的,能保证所有线程都被同一把锁阻塞:
@Override public void run() { ... // add row to excel sheet synchronized(RootQueryThread.class) { Row row = sheet.createRow(querySequence+1); row.createCell(0).setCellValue(rqResult.getRQName()); // 其他单元格操作 } }
这种方式锁粒度比用sheet大,会锁住所有RootQueryThread实例,适合sheet实例难以获取的场景,但性能略逊于方案1。
方案3:提前创建所有行(性能最优)
如果你的行号是预先确定的(比如querySequence是连续的0、1、2...),可以在主线程中一次性创建好所有需要的行,线程只负责填充对应行的数据,彻底避免多线程修改sheet的场景:
// 主线程提前创建所有行 for (int i = 0; i < totalThreadCount; i++) { sheet.createRow(i + 1); } // 线程中仅填充数据,无需创建行 @Override public void run() { ... Row row = sheet.getRow(querySequence + 1); // 直接获取已创建的行 row.createCell(0).setCellValue(rqResult.getRQName()); // 其他单元格操作 }
这种方式不需要线程等待锁,性能最好,但前提是你能提前确定需要创建的总行数。
额外提醒
Apache POI的XSSFSheet、Workbook等核心对象都不是线程安全的,任何修改操作(创建行/单元格、修改单元格内容等)都必须在单线程环境下执行,或者通过全局锁确保互斥访问。
内容的提问来源于stack exchange,提问作者Paul Ning

