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インスタンスを作成する上記のアプローチを使用することで、パフォーマンスが向上しますか?

編集 :

問題に直面する可能性があるのは、次のXMLファイルの解析に影響を与える可能性のあるXMLファイルの解析中にDocumentBuilderが状態情報を保存する場合のみです。

4

5 に答える 5

42

同じ問題に関する他の質問については、コメントセクションを参照してください。あなたの質問に対する簡単な答え:いいえ、これらのクラスをシングルトンに入れるのはOKではありません。DocumentBuilderFactoryもDocumentBuilderもスレッドセーフであることが保証されていません。XMLを解析するスレッドが複数ある場合は、各スレッドに独自のバージョンのDoumentBuilderがあることを確認してください。リセット後にDocumentBuilderを再利用できるため、スレッドごとに1つだけ必要です。

同じDocumentBuilderを使用するのは悪いことを示す小さなスニペットを編集します。java 1.6_u32および1.7_u05では、このコードは。で失敗しorg.xml.sax.SAXException: FWK005 parse may not be called while parsingます。Builderで同期のコメントを解除すると、正常に機能します。

        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固有である可能性があります。IBMjavaまたは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を作成できる必要があります。DocumentBuilderのプールを作成することもできます。

たとえば、静的メソッドDocumentBuilderFactory.newInstanceがスレッドセーフであると言っているところはどこにも見つかりません。実装は、メソッドの同期が行われているという点でスレッドセーフのように見えますが、仕様では、DocumentBuilderFactory.newDocumentBuilderがスレッドセーフであると具体的に述べています。

于 2015-03-25T04:31:19.463 に答える
2

あなたは3つのことを知る必要があります:

  1. 工場を作るための費用はいくらですか?コストが低い場合、パフォーマンスの向上はゼロに近い可能性があります。
  2. ビルダーを作成するためのコストはいくらですか?コストが低い場合、パフォーマンスの向上はゼロに近い可能性があります。
  3. ファクトリおよび/またはビルダースレッドは安全ですか?synchronizedそうでない場合は、それらにアクセスするメソッドがキーワードを使用してスレッドセーフになっていることを確認する必要があります。

私はあなたが使用しているDocumentBuilderクラスに精通していませんが、このすべての情報はそのjavadocまたは他のドキュメントで利用可能であるはずです。特定のオブジェクトの作成にコストがかかる場合、通常、これらのオブジェクトはこの情報をあなたに投げかけます。

于 2012-09-17T08:39:05.033 に答える
1

主にあなたの質問に答えるために、DocumentBuilderはスレッドセーフではありません。ただし、次の2つの方法でスレッドセーフにすることができます。

  • 同期
  • ThreadLocal

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インスタンスを再利用して、毎回作成するのではないでしょうか。新しいオブジェクトの作成はCPU使用率の3%を占めています。

サンプル:-

クラスレベル変数:-プライベート静的揮発性DocumentBuilderビルダー= 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 に答える