美文网首页
Java随笔(1) 线程池使用异常

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

作者: sunyelw | 来源:发表于2020-03-21 11:41 被阅读0次

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


    先上代码(稍作修改)

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

    吾日三省。

    相关文章

      网友评论

          本文标题:Java随笔(1) 线程池使用异常

          本文链接:https://www.haomeiwen.com/subject/ovkyyhtx.html