重新使用 DOM 模板时 Saxon 不是线程安全的

Saxon not thread safe when re-using a DOM template

我正在试用 Saxon HE 作为内置 JAXP 实现的替代品,我 运行 遇到了它的各种线程问题。

出于性能原因,我将模板缓存为 DOM 文档并像这样跨线程重用它们

        TransformerFactory factory = TransformerFactory.newInstance();
        Document template = getFromCache(...);
        Transformer transformer = factory.newTransformer(new DOMSource(template));
        transformer.transform(new DOMSource(document), streamResult);

我知道 DOM 不能保证是线程安全的,但是 JAXP XSLT 实现在 Saxon 完全崩溃时运行良好。怎么会这样?

有没有办法让它正常工作,但为了性能而保留缓存?

Saxon 中的异常发生在不同的地方,但最常见于:

java.lang.NullPointerException: null
at com.sun.org.apache.xerces.internal.dom.ParentNode.nodeListItem(ParentNode.java:786) ~[na:1.7.0_71]
at com.sun.org.apache.xerces.internal.dom.ParentNode.item(ParentNode.java:800) ~[na:1.7.0_71]
at net.sf.saxon.dom.DOMSender.walkNode(DOMSender.java:154) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMSender.outputElement(DOMSender.java:243) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMSender.walkNode(DOMSender.java:162) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMSender.outputElement(DOMSender.java:243) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMSender.walkNode(DOMSender.java:162) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMSender.outputElement(DOMSender.java:243) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMSender.walkNode(DOMSender.java:162) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMSender.outputElement(DOMSender.java:243) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMSender.walkNode(DOMSender.java:162) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMSender.outputElement(DOMSender.java:243) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMSender.walkNode(DOMSender.java:162) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMSender.send(DOMSender.java:92) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.dom.DOMObjectModel.sendSource(DOMObjectModel.java:250) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.event.Sender.send(Sender.java:221) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.style.StylesheetModule.loadStylesheetModule(StylesheetModule.java:128) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.style.Compilation.compilePackage(Compilation.java:131) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.style.Compilation.compileSingletonPackage(Compilation.java:94) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.s9api.XsltCompiler.compile(XsltCompiler.java:543) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.jaxp.SaxonTransformerFactory.newTemplates(SaxonTransformerFactory.java:152) ~[Saxon-HE-9.6.0-7.jar:na]
at net.sf.saxon.jaxp.SaxonTransformerFactory.newTransformer(SaxonTransformerFactory.java:108) ~[Saxon-HE-9.6.0-7.jar:na]

不知道是什么原因导致您描述的错误,但是 javax.xml.transformTemplates class 这是以线程安全的方式重用样式表并避免为每次转换创建和解析它们的开销。

  1. 使用 TransformerFactory.newTemplates(Source).
  2. 使缓存存储 Templates 而不是模板源的 DOM 文档
  3. 对缓存的模板对象调用 Templates.newTransformer() 以获得单个转换的转换器。

这甚至应该比您当前的解决方案更快。


编辑:

NPE 似乎源自 Xerces 中的一行,内容如下:

fNodeListCache.fChildIndex = i;

在我看来,这是一个强有力的线索,表明对 DOM 的多线程访问导致了错误,而不是 Saxon。不知道为什么默认的 TransformerFactory 在这种情况下幸存下来。

你说:"For performance reasons I cache templates as DOM documents and reuse them across threads"。但这根本不会帮助提高性能。它只会为您节省 I/O 和 XML 解析成本,这只是编译样式表成本的一小部分。您应该缓存 Templates 对象,它是样式表的编译表示。这不仅提供了更有效的缓存,而且恰好是线程安全的。

也许内置的 JAXP 实现通过自己的同步来弥补 DOM 中线程安全的不足。这样做当然是可能的,但这不是一个明智的用例,因此似乎没有必要。

您还应该知道,如果您像您所说的那样关心性能,那么使用 Saxon 以 DOM 形式提供实例文档的效率非常低 - 通常比 Saxon 慢 5-10 倍允许 Saxon 使用其内部树格式。