美文网首页
记一次多线程奇怪的现象

记一次多线程奇怪的现象

作者: idelo | 来源:发表于2018-09-03 14:02 被阅读0次

做了一个数据交换的程序,程序中使用JAXB来进行xml文档的解析,上级要求程序可以配置解析的线程数量,以在高峰值的时候提高解析速度——此为背景说明。
某天,测试跑来和我说,你这程序的处理效率太低了,单线程1分钟才处理了120条数据,增加了10条线程,也就是130条,处理能力没有明显的提升,这样不行啊。


处理速度慢

我就很奇怪了,第一个反应是是不是代码敲错了多线程没有启动,还是单线程在跑。又或者有哪个方法是同步的造成了线程的等待。

对于第一种猜测,我在运行时增加了日志,在一些节点上增加了Thread.sleep(),通过日志判断确实是有多个线程在运行解析任务,排除这个原因。
对于第二种猜测,在log的基础上查看各个方法的用时。发现了一个很奇怪的现象。用JAXB把string类型的xml文档转成对应的pojo,这个方法在只有1个线程的情况下只要30毫秒,而10个线程的情况下居然要3秒多才能运行完。也就是说这个方法的耗时是不是一个常数值,他会随着线程数线性增长!

Package xmlPackage = jaxbUtil.convertXmlStrToJavaBean(messageContent, path, Package.class);

解决思路

找到原因了,那么问题就解决了一半,先看一下这个方法的内部实现

    public static <T> T convertXmlStrToJavaBean(String xmlStr, String xmlExpress, Class<T> clazz)
            throws XPathExpressionException,ParserConfigurationException, SAXException,IOException,JAXBException {
        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document document = builder.parse(new ByteArrayInputStream(xmlStr.getBytes()));
        XPath xpath = XPathFactory.newInstance().newXPath();
        Node widgetNode = (Node) xpath.evaluate(xmlExpress, document, XPathConstants.NODE);

        JAXBContext context = JAXBContext.newInstance(clazz);
        Binder<Node> binder = context.createBinder();
        return (T)binder.unmarshal(widgetNode);
    }

分析一下这个方法,每次调用的时候DocumentBuilder 、XPath 、JAXBContext 、Binder。4个东西都是需要重新创建的,而我的clazz对象是固定的Package.class所以应该也能抽取出来。所以我的第一版改进就是把这4个对象定义成了类的私有成员变量,方法改为非静态了,然后新建了一个jaxbUtil类的bean,把他注入了spring中。
其实, JAXBContext context = JAXBContext.newInstance(clazz); 只有这句语句运行时间会增长,其他语句的初始化时间基本可以忽略。具体为什么实例化的时间会随着线程数的增加而增加,我也没弄明白。


多线程

再次测试,速度已经起飞了10个线程500条数据2秒不到就处理完了。愉快的提交了测试~
过了一会测试再次反馈,速度是达标了,但是出现了无法解析的错误,需要赶紧处理。我拉下数据一看,各种问题有空指针,非法参数,考虑到我刚刚的修改,只了初始化了一个对象却有多个线程需要用,会不会线程不安全呢。于是我调了一个异常

org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
        at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:263)
        at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:28)
        at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:208)

从程序逻辑来看,如果当前DocumentBuilder对象正在转换文档,此时再次请求转换文档,那么直接抛出XNIException(“FWK005 parse may not be called while parsing.”);异常。
这个问题也比较好解决,一种是对转换xml文档的方法,增加synchronized关键字,这样子不会有两个线程同时访问方法。
还有一种方法是创建一个DocumentBuilder类型的ThreadLocal变量,这样子每个线程都拥有自己的
DocumentBuilder对象,能够同时转换多个xml文件。代码如下:

private static ThreadLocal docBuildeIns = new ThreadLocal() {
    protected DocumentBuilder initialValue() {
        try {
            return DocumentBuilderFactory.newInstance().newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            String msg = "DocumentBuilder 对象初始化失败!";
            log.error(msg, e);
            throw new IllegalStateException(msg, e);
        }
    } 
};

ok了同步是肯定不能用了,定义ThreadLocal变量吧,跟着这个思路,改了4个ThreadLocal变量,问题得到完美的解决!


内存溢出

一小时之后测试又过来找我了,这次手上提了把刀…… 测试反应在前20分钟,程序运行的很好,但是随着时间的推移,运行的越来越慢,直到最后几乎就不运行了。
我尝试在本地进行复现,在分析log日志的时候我发现了java.lang.OutOfMemoryError:Java heap space。啊堆溢出了,我们知道JVM中的堆存放了大量的对象,应该是有某个对象使用了但是没有进行释放。

怀疑1:

观察到使用了字节流但是没有关闭,经过查找资料发现这个流是内存读写流,在不使用时Java的垃圾回收机制会将它回收,不需要手动关闭。经过试验,也确实不是他的事。

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xmlStr.getBytes());

怀疑2:
有某个变量不使用没释放,或者被static化了却没有清空。
借助MemoryAnalyzer工具进行内存的分析。在这里可以看出com.sun.xml.internal.bind.v2.runtime.AssociationMap这个Map占用了大量的内存,找到对应的类,发现在实例化Binder的时候会创建AssociationMap缓存xml节点,而Binder已经被我做成单例的bean了,随着处理的xml越来越多AssociationMap当然越来越大= =


图片.png
解决思路

既然Binder无法重复使用,那么每次都实例化一下,实例化的时间成本大约在2ms左右,时间也不会变动,不影响效率。

    Binder<Node> binder = JAXBContextThreadLocal.get().createBinder();

这里篇文章主要还是记录一下多线程中ThreadLocal的用法,在多线程的时候需要多考虑一下调用的方法是否是线程安全的。

感谢以下文章作者
Java并发编程:深入剖析ThreadLocal
ThreadLocal-面试必问深度解析

相关文章

  • 记一次多线程奇怪的现象

    做了一个数据交换的程序,程序中使用JAXB来进行xml文档的解析,上级要求程序可以配置解析的线程数量,以在高峰值的...

  • 一次奇怪的现象...

    在上周的测试过程中,发现随着程序运行时间的增长[速度不快],每个port的数据包miss高达千万级别的,也就是能处...

  • 奇怪的现象

    奇怪的现象之一:初一初二是班主任奇缺,请这个不干,请那个不干,到了初三出现一个奇怪的现象,大家争着干,没有聘到说自...

  • 奇怪的现象

    我上班的公司经历不多,但是我突然发现,为什么我上班的地方总会有一个同事特爱开一些黄色玩笑,或者会跟我或者其...

  • 奇怪的现象

    现在是晚上11:43,我刚洗完澡,在泡脚。为什么每天都泡脚呢?从我记事的时候,我每天晚上都会洗脚。当然不是简简单单...

  • 奇怪的现象

    今天带着小孩去美院踢足球,看到一个爸爸带着一个九岁左右的小孩在足球场,这位小孩在沿着一条线练习带球,爸爸在球场边,...

  • 奇怪的现象

    今天遁入虚无 遗留下灵魂在发声 路人有许多 都背着重重的壳 步履缓慢的前行者 每...

  • 奇怪的现象

    今年,一直在看FT的文章,无外乎批评中国大陆在国内和国际上的各种举动。在他眼里,中国这个最大的发展中国家是人类公敌...

  • 奇怪的现象

    这次离开安徽之前,分别用电话、微信、面对面的与朋友们告别,因为是乘飞机,所以特别告诉他们,我们是坐飞机出行,就怕对...

  • 奇怪的现象

    今天跟我的一个很理性女性朋友聊起一个关于“出轨”、“婚姻背叛”的话题。 我问她:“假如以后你结婚了,老公出轨了,你...

网友评论

      本文标题:记一次多线程奇怪的现象

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