欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

编码最佳实践(3)--尽量重用昂贵的初始化对象 编码最佳实践重用初始化 

程序员文章站 2022-06-04 14:00:30
...
    这里将要讲述的是一系列的类似案例,都是在各个产品进行performance tuning时被发现的,非常具有普适性。可以说在日常开发中,有非常大的概率遇到相同或者类似的情形,因此需要对其保持警惕以便避免陷入类似的性能问题。

    我们从JAXBContext这个对象开始,JAXBContext 是JAXB API的入口,典型的代码实现如下:
   
private void unmarshal() {
    JAXBContext context = JAXBContext.newInstance(DirectoryConstants.JAXB_CONTEXT_CLASS);
    Unmarshaller u = context.createUnmarshaller();
    Object obj = u.unmarshal(...);
}


    这个是标准使用流程了,首先初始化JAXBContext对象,通过JAXBContext对象创建Unmarshaller对象,然后使用Unmarshaller对象来进行unmarshal操作。

    这个写法在功能实现上没有任何问题,但是如果一旦进行压力测试,就会暴露性能问题。JAXBContext对象的初始化是个资源消耗非常大的操作,我们可以通过threaddump进行分析,会发现很多工作线程都在执行JAXBContext.newInstance()这个方法,而不是我们期待的u.unmarshal()。而事实上JAXBContext对象的Sun实现是线程安全的,即容许多线程同时调用一个JAXBContext对象的createUnmarshaller(),因此完全没有必要为每个xml的 marshaling 和 unmarshalling 操作去初始化一次JAXBContext对象。可以参考这里的说明:https://jaxb.dev.java.net/faq/index.html#threadSafety

    简单点说,这个一个初始化代价昂贵,却又可重复使用的对象。

    因此,我们只需要初始化JAXBContext对象一次并保存起来,然后重复使用这个JAXBContext对象即可。保存JAXBContext对象的方式可以有很多种,比如cache / threadlocal,或者使用一个单例来维持这个对象。比如下面的代码示例:

private void unmarshal() {
    JAXBContext context = JAXBContextHolder.get();
    ...
}

public class JAXBContextHolder {
    private static final JAXBContext instance = JAXBContext.newInstance(DirectoryConstants.JAXB_CONTEXT_CLASS);

    public static JAXBContext get() {
        return instance;
    }
}


    在实际项目中, 通过上面的简单改进之后,我们当时得到了一个非常巨大的回报:TPS (transactions per second 每秒事务处理量)直接*3 !!

    这个案例从技术上讲非常的简单,道理很浅显,相信每个人都能轻松理解。或者说,这是一个“知道了就简单,不知道就容易犯错而不自知”的地方,因此依然有些东西需要注意:

(1) 有哪些对象有类似的特性

    目前发现的类似对象有
    1. 刚刚上面讲到的JAXB API中的 JAXBContext 对象
    2. SOAP API中的javax.xml.soap.SOAPFactory
    3. CFX client

    通常情况下,在使用各种api或者工具类库时,如果发现调用代码中有类似的初始化语句,都应该稍加注意(除非明确当前代码对性能完全没有要求),可以去查一下这个类对象的javdoc或者直接看源码,如果发现满足上面所说的特性,则应该考虑进行上述的性能优化。

    根据经验,类似的初始化对象通常的命名规则都是***Context/***Factory之类,或者***client,遇到类似名字时需要提高警惕。

(2)假设问题已经存在,如果才能在performance tuning中迅速发现问题的代码?

    通常的办法就是用thread dump,一般连续dump个3-5次,然后通过分析thread dump信息 (推荐使用eclipse插件 lockness),看当前请求的线程(通常是一个线程池)都在干什么。一般初始化昂贵都昂贵在类似文件IO操作或者加锁之类的地方,很容易在thread dump中被发现。