Java随笔(1) 线程池使用异常

昨天晚上定位个问题花了好久,诶,还是菜啊~


先上代码(稍作修改)

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 的实现,但还是会犯这种"低级错误",这提醒我敲代码时应该要更加细心点,多想想,谋定而后动。

吾日三省。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容