代码之家  ›  专栏  ›  技术社区  ›  Cratylus

基于DOM API的Java多线程

  •  3
  • Cratylus  · 技术社区  · 14 年前

    我有一个执行XML解析的函数。我想使函数线程安全,但也尽可能优化(减少阻塞)。
    简短的代码如下:

    public Document doXML(InputStream s)
    {
    //Some processing.
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder parser = factory.newDocumentBuilder();
      Document xmlDoc = parser.parse(is);
      return xmlDoc;
    
    }
    

    但我不想在每次调用中创建新的DocumentBuilderFactory或DocumentBuilder。
    我想重用工厂和解析器,但我不确定它们是线程安全的。那么,最理想的方法是什么呢?
    1) 在类字段中缓存DocumentBuilderFactory,并同步factory.newDocumentBuilder();以便每个线程都有自己的DocumentBuilder实例
    2) 缓存DocumentBuilderFactory 文档生成器和 同步parser.parse(is); 每个线程
    我认为(2)是最好的,但这样做安全吗?我还可以避免同步阻塞吗?我希望尽可能快。

    谢谢?

    4 回复  |  直到 14 年前
        1
  •  4
  •   John Vint    14 年前

    如果要重用线程(如线程池中),则可以将DocumentBuilderFactory声明为线程本地的。为每个线程创建一个新的集合是有开销的,但是正如我所说,如果您要重用后续的开销是非常低的。

    final ThreadLocal<DocumentBuilderFactory> documentBuilderFactor = new ThreadLocal<DocumentBuilderFactory>(){
         public DocumentBuilderFactory  initialValue(){
           return  DocumentBuilderFactory.newInstance();
         }
    }
    
    public Document doXML(InputStream s)
    {
    //Some processing.
      DocumentBuilderFactory factory = documentBuilderFactor.get();
      DocumentBuilder parser = factory.newDocumentBuilder();
      Document xmlDoc = parser.parse(is);
      return xmlDoc;
    
    }
    

    在这里,您将只为每个线程创建一个DocumentBuilderFactory。

    我不知道在解析时documentbuilder是否是线程安全的(它是不可变的吗?)但是,如果在解析时documentbuilder是线程安全的,那么您可以使用与我所说的相同的机制。

    这个解决方案将使总吞吐量尽可能快。

    注意:这个没有经过测试或编译,只是给出了我所指的概念。

        2
  •  2
  •   Qwerky    14 年前

    2)是线程安全的,但是你的应用程序一次只能解析一个文档。

    为什么不使用你的代码呢?做

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder parser = factory.newDocumentBuilder();
    

    有明显不可接受的开销吗?

        3
  •  1
  •   khachik    14 年前

    如果要避免同步阻塞,应确保使用原子操作。行为 javax.xml.parser.* 取决于实现(您可以使用系统属性指定实现,或者调用实现代码)。根据线程数和每个线程的负载权重,控制解析器对象的创建可能是合理的。您应该在创建新的解析器或等待解析器之间进行选择。代码可以在启动时创建一个解析器池,然后线程从池中获取解析器,当没有空闲解析器时,池会阻塞解析器池。一旦一个线程获得了一个解析器,它就会解析数据,重置解析器并将其放回池中。您总是可以通过池的长度来控制时间/内存的使用。

        4
  •  1
  •   John M    14 年前

    在类似的情况下,我遇到了一些性能问题。我在每次使用时都创建工厂对象以避免线程问题(每秒10次)。该(公认是旧的)平台中的XML实现为服务提供者类执行了一些相对较慢的查找逻辑。

    我的调优是确定结果的答案,并通过命令行属性对其进行配置。这导致跳过了查找。

    -Djavax.xml.parsers.DocumentBuilderFactory=com.example.FactoryClassName
    -Djavax.xml.transform.TransformerFactory=com.example.OtherFactoryClassName
    

    令人沮丧的是,如果找到一个类,查找代码就具有缓存逻辑。但没有缓存未命中(未找到任何内容,请使用默认值)。稍微好一点的查找缓存(处理了否定的情况)会使这种情况变得不必要。

    这还需要吗?需要在您的环境中进行测试。我在Solaris上使用truss来注意到查找逻辑导致的非常频繁的文件操作。