Java DocumentBuilder线程安全吗?

Java DocumentBuilder线程安全吗?,java,multithreading,performance,dom,singleton,Java,Multithreading,Performance,Dom,Singleton,我正在查看的当前代码库使用DOM解析器。以下代码片段在5种方法中重复: DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); 如果包含上述代码的方法在循环中被调用,或者该方法在应用程序中被多次调用,我们将承担为每次调用此类方法创建新DocumentBuilderFactory实例和新Docum

我正在查看的当前代码库使用DOM解析器。以下代码片段在5种方法中重复:

 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 DocumentBuilder builder = factory.newDocumentBuilder();
如果包含上述代码的方法在循环中被调用,或者该方法在应用程序中被多次调用,我们将承担为每次调用此类方法创建新DocumentBuilderFactory实例和新DocumentBuilder实例的开销

围绕DocumentBuilder工厂和DocumentBuilder实例创建一个单例包装器是否是一个好主意,如下所示:

public final class DOMParser {
   private DocumentBuilderFactory = new DocumentBuilderFactory();
   private DocumentBuilder builder;

   private static DOMParser instance = new DOMParser();

   private DOMParser() {
      builder = factory.newDocumentBuilder();
   }

   public Document parse(InputSource xml) {
       return builder.parser(xml);
   }
}
如果在多个线程之间共享上述单例,是否会出现任何问题?如果没有,那么在应用程序的整个生命周期中,使用上述方法仅创建一次DocumentBuilderFactory和DocumentBuilder实例是否会提高性能

编辑:


我们唯一可以面对的问题是DocumentBuilder在解析XML文件时是否保存了一些状态信息,这可能会影响下一个XML文件的解析

你需要知道三件事:

  • 创建工厂的成本是多少?如果成本低,您的性能增益可能接近于零
  • 创建生成器的成本是多少?如果成本低,您的性能增益可能接近于零
  • 工厂和/或建筑商是否线程安全?如果没有,则需要使用
    synchronized
    关键字确保访问它们的方法是线程安全的

  • 我不熟悉您正在使用的DocumentBuilder类,但所有这些信息都应该在其javadoc或其他文档中提供。如果创建某些对象的成本很高,他们通常会向您抛出这些信息。

    有关同一问题的其他问题,请参阅评论部分对您的问题的简短回答:不,将这些类放在单个项中是不合适的。DocumentBuilderFactory和DocumentBuilder都不能保证线程安全。如果有多个线程解析XML,请确保每个线程都有自己的DoumentBuilder版本。每个线程只需要其中一个,因为可以在重置DocumentBuilder后重新使用它

    编辑一个小片段,说明使用同一个DocumentBuilder是不好的。对于java 1.6_u32和1.7_u05,此代码在
    org.xml.sax.SAXException时失败:解析时可能不会调用FWK005 parse
    。在生成器上取消注释同步,并且工作正常:

            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            final DocumentBuilder builder = factory.newDocumentBuilder();
    
            ExecutorService exec = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 10; i++) {
                exec.submit(new Runnable() {
                    public void run() {
                        try {
    //                        synchronized (builder) {
                                InputSource is = new InputSource(new StringReader("<?xml version=\"1.0\" encoding=\"UTF-8\" ?><俄语>данные</俄语>"));
                                builder.parse(is);
                                builder.reset();
    //                        }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            exec.shutdown();
    
    DocumentBuilderFactory=DocumentBuilderFactory.newInstance();
    final DocumentBuilder=factory.newDocumentBuilder();
    ExecutorService exec=Executors.newFixedThreadPool(10);
    对于(int i=0;i<10;i++){
    exec.submit(新的Runnable(){
    公开募捐{
    试一试{
    //同步(生成器){
    InputSource is=新的InputSource(新的StringReader(“аааф”);
    builder.parse(is);
    builder.reset();
    //                        }
    }捕获(例外e){
    e、 printStackTrace();
    }
    }
    });
    }
    exec.shutdown();
    
    下面是您的答案-不要从多个线程调用
    DocumentBuilder.parse()
    。是的,这种行为可能是特定于JRE的,如果您使用的是IBM java或JRockit,或者给它一个不同的DocumentBuilderImpl,它可能工作得很好,但是对于默认的xerces实现,它不会工作。

    JAXP规范(v1.4)说:

    预计SAXParserFactory实现的newSAXParser方法、DocumentBuilderFactory的newDocumentBuilder方法和TransformerFactory的newTransformer方法将是线程安全的,不会产生副作用。这意味着应用程序程序员应该能够从共享工厂一次在多个线程中创建transformer实例,而不会产生副作用或问题

    因此,例如,您应该能够通过DocumentBuilderFactory.newInstance创建单个DocumentBuilderFactory实例,然后通过DocumentBuilderFactory.newDocumentBuilder使用该单个工厂为每个线程创建一个DocumentBuilder。您还可以创建一个DocumentBuilder池


    我找不到任何地方表明,例如,静态方法DocumentBuilderFactory.newInstance是线程安全的。实现看起来是线程安全的,因为有一些方法同步正在进行,但规范特别指出DocumentBuilderFactory.newDocumentBuilder是线程安全的。

    主要是为了回答您的问题,不,Document Builder不是线程安全的。但我们可以通过两种方式使其线程安全:

    • 同步的
    • 线程本地
    对于Synchronized,我们所能做的就是制作一个同步块,这对我们来说是可行的,我们应该在小的块上使用Synchronized,因为它非常昂贵,有时会使事情变得非常缓慢

       DocumentBuilder documentBuilder=DocumentBuilderFactory.newInstance().newDocumentBuilder();
       synchronized(documentBuilder)
       {
          documentBuilder.parse(xmlFile.getInputStream());           
       }
    
    我们可以遵循的另一个更好的方法是使用ThreadLocal

        public class XmlParser {
    
        private static ThreadLocal<DocumentBuilder> documentBuilder;
    
        public XmlParser() {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            documentBuilder = ThreadLocal.withInitial(() -> documentBuilder(factory));
        }
        private DocumentBuilder documentBuilder(DocumentBuilderFactory factory) {
            try {
                return factory.newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw new Exception("file is not valid);
            }
        }
        public Document parse(MultipartFile xmlFile) {
            try {
                Document parse = documentBuilder.get().parse(xmlFile.getInputStream());
                documentBuilder.remove();
                parse.normalizeDocument();
                return parse;
    
            } catch (IOException | SAXException e) {
                throw new Exception(e);
            }
        }
    
    公共类XmlParser{
    私有静态线程本地文档生成器;
    公共XmlParser(){
    DocumentBuilderFactory工厂=DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(true);
    documentBuilder=ThreadLocal.withInitial(()->documentBuilder(factory));
    }
    私人DocumentBuilder DocumentBuilder(DocumentBuilder工厂){
    试一试{
    返回factory.newDocumentBuilder();
    }捕获(ParserConfiguration异常e){
    抛出新异常(“文件无效”);
    }
    }
    公共文档解析(多部分文件xmlFile){
    试一试{
    documentparse=documentBuilder.get().parse(xmlFile.getInputStream());
    documentBuilder.remove();
    parse.normalizeDo
    
        if (Common.builder == null) {
            synchronized (DocumentBuilder.class) {
                if (Common.builder == null) {
                    SplunkLogger.info("DocBuilderInstance=New_Instance");
                    Common.builder =
                            XMLUtil.getDocumentBuilderFactory()
                            .newDocumentBuilder(); // DocumentBuilderFactory.newInstance().newDocumentBuilder();
                } else {
                    SplunkLogger.info("DocBuilderInstance=Re-Use_Existing_Instance_InnerIf");
                }
            }
        } else {
            SplunkLogger.info("DocBuilderInstance=Re-Use_Existing_Instance");
    }
        final InputSource source = new InputSource();
        source.setCharacterStream(new StringReader(responseString));
        final Document doc = Common.builder.parse(source);
        return doc.getElementsByTagName(firstKey);
    }