48

我正在查看的当前代码库使用 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 文件的解析。

4

5 回答 5

42

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

编辑一个小片段,表明使用相同的 DocumentBuilder 是不好的。对于 java 1.6_u32 和 1.7_u05,此代码失败并显示org.xml.sax.SAXException: FWK005 parse may not be called while parsing. 在构建器上取消注释同步,它工作正常:

        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();

所以这是你的答案 - 不要DocumentBuilder.parse()从多个线程调用。是的,这种行为可能是特定于 JRE 的,如果您使用的是 IBM java 或 JRockit 或给它一个不同的 DocumentBuilderImpl,它可能工作正常,但对于默认的 xerces 实现 - 它不会。

于 2012-09-17T08:52:06.717 回答
20

JAXP 规范 (V 1.4) 说:

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

https://jaxp.java.net/docs/spec/html/#plugabililty-thread-safety

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

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

于 2015-03-25T04:31:19.463 回答
2

你需要知道三件事:

  1. 创建工厂的成本是多少?如果成本低,您的性能增益可能接近于零。
  2. 创建构建器的成本是多少?如果成本低,您的性能增益可能接近于零。
  3. 工厂和/或建造者线程安全吗?如果没有,您需要确保使用synchronized关键字使访问它们的方法成为线程安全的。

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

于 2012-09-17T08:39:05.033 回答
1

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

  • 同步的
  • 线程本地

对于 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);
        }
    }
于 2020-08-14T14:38:40.300 回答
0

我们是否可以重用 DocumentBuilder 实例而不是每次都创建它,因为我们的新对象创建占用了 3% 的 CPU 利用率。

样本:-

类级变量:- private static volatile DocumentBuilder builder = null;

    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);
}
于 2020-10-16T06:14:38.713 回答