昨天晚上定位个问题花了好久,诶,还是菜啊~
先上代码(稍作修改)
private <T> List<T> parseFile(String fileName, String filePath, int number, List<String> errList) {
List<T> userList = new ArrayList <>(number);
String nowPath = filePath + "/" + fileName;
// es
int ths = TaskUtil.getThs(number, EVERY_SIZE);
ExecutorService tape = TaskUtil.getEs(ths, "parse-" + fileName);
try (BufferedReader bs =
new BufferedReader(
new InputStreamReader(
new FileInputStream(nowPath), "GBK"))) {
String line;
while ((line = bs.readLine()) != null) {
String finalLine = line;
tape.execute(() -> {
T bu;
if ((bu = parseLine(finalLine, errList)) != null) userList.add(bu);
});
}
return userList;
} catch (Exception e) {
log.error("ag_error_parse_file filePath: {}", nowPath, e);
return new ArrayList <>();
} finally {
tape.shutdown();
}
}
需求描述:多线程解析一个文件,存到一个List中返回。
前提:不想显示定义线程类,比如Callable或Runnable的实现,减少代码量
问题描述:解析没问题,但返回的List时空时不空。
执行流程简化
- 创建线程池
- 循环将任务加入线程池
- 结果塞入集合中
- 返回这个集合
这里总共有两个致命问题
- ArrayList 不是一个线程安全的集合,在这种情况下使用存在并发风险
- 返回集合时,线程池可能还没执行结束,导致返回的集合中数据不全
改动一下
private <T> List<T> parseFile(String fileName, String filePath, int number, List<String> errList) {
List<T> userList = Collections.synchronizedList(new ArrayList <>());
String nowPath = filePath + "/" + fileName;
// es
int ths = TaskUtil.getThs(number, EVERY_SIZE);
ExecutorService tape = TaskUtil.getEs(ths, "parse-" + fileName);
try (BufferedReader bs =
new BufferedReader(
new InputStreamReader(
new FileInputStream(nowPath), "GBK"))) {
String line;
while ((line = bs.readLine()) != null) {
String finalLine = line;
tape.execute(() -> parseLine(finalLine, errList, userList));
}
tape.shutdown();
while (true) {
if (tape.isTerminated()) {
return userList;
}
Thread.sleep(100);
}
} catch (Exception e) {
log.error("ag_error_parse_file filePath: {}", nowPath, e);
return new ArrayList <>();
} finally {
if (!tape.isTerminated()) tape.shutdownNow();
}
}
针对以上两点做的改动
- List 改成线程安全的集合,原理是使用
synchronized
关键字对集合操作上锁,类似Vector
(在某些情况这种方式仍不够安全,需要使用CopyOnWriteArrayList
) - 执行流程改为等线程池结束后再返回目标集合,确保了数据的完整
我看过ArrayList的源码,也看过 ThreadPoolExecutor 的实现,但还是会犯这种"低级错误",这提醒我敲代码时应该要更加细心点,多想想,谋定而后动。
吾日三省。
网友评论