DocumentBuilder 线程安全吗?

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


答案 1

有关同一事项的其他问题,请参阅评论部分。对你的问题的简短回答:不,把这些类放在一个单例中是不行的。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();

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


答案 2

JAXP 规范 (V 1.4) 说:

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

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

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

我找不到任何地方说,例如,静态方法DocumentBuilderFactory.newInstance是线程安全的。该实现似乎是线程安全的,因为正在执行一些方法同步,但规范特别指出DocumentBuilder是线程安全的。