web-dev-qa-db-fra.com

Le thread DocumentBuilder est-il sûr?

La base de code actuelle que j'examine utilise l'analyseur DOM. Le fragment de code suivant est dupliqué en 5 méthodes:

 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 DocumentBuilder builder = factory.newDocumentBuilder();

Si une méthode qui contient le code ci-dessus est appelée dans une boucle ou si la méthode est appelée plusieurs fois dans l'application, nous supportons la surcharge de création d'une nouvelle instance DocumentBuilderFactory et d'une nouvelle instance DocumentBuilder pour chaque appel à une telle méthode.

Serait-ce une bonne idée de créer un wrapper singleton autour de la fabrique DocumentBuilder et des instances DocumentBuilder comme indiqué ci-dessous:

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

Des problèmes peuvent-ils survenir si le singleton ci-dessus est partagé sur plusieurs threads? Sinon, y aura-t-il un gain de performances en utilisant l'approche ci-dessus de créer les instances DocumentBuilderFactory et DocumentBuilder une seule fois pendant la durée de vie de l'application?

Modifier:

La seule fois où nous pouvons faire face à un problème est si DocumentBuilder enregistre certaines informations d'état lors de l'analyse d'un fichier XML, ce qui peut affecter l'analyse du fichier XML suivant.

34
CKing

Voir la section des commentaires pour d'autres questions sur le même sujet. Réponse courte à votre question: non, ce n'est pas le cas ok pour mettre ces classes dans un singleton. Ni DocumentBuilderFactory ni DocumentBuilder ne sont garantis pour être thread-safe. Si vous avez plusieurs threads analysant XML, assurez-vous que chaque thread a sa propre version de DoumentBuilder. Vous n'en avez besoin que d'un par thread, car vous pouvez réutiliser un DocumentBuilder après l'avoir réinitialisé.

MODIFIER Un petit extrait pour montrer que l'utilisation du même DocumentBuilder est mauvaise. Avec Java 1.6_u32 et 1.7_u05 ce code échoue avec org.xml.sax.SAXException: FWK005 parse may not be called while parsing. Décommentez la synchronisation sur le générateur, et cela fonctionne très bien:

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

Voici donc votre réponse - n'appelez pas DocumentBuilder.parse() à partir de plusieurs threads. Oui, ce comportement peut être spécifique à JRE, si vous utilisez IBM Java ou JRockit ou donnez-lui un DocumentBuilderImpl différent, cela pourrait fonctionner correctement, mais pour l'implémentation par défaut de xerces - ce n'est pas le cas.

33
Denis Tulskiy

La spécification JAXP (V 1.4) dit:

Il est prévu que la méthode newSAXParser d'une implémentation SAXParserFactory, la méthode newDocumentBuilder d'un DocumentBuilderFactory et la méthode newTransformer d'un TransformerFactory seront thread-safe sans effets secondaires. Cela signifie qu'un programmeur d'application devrait s'attendre à pouvoir créer des instances de transformateur dans plusieurs threads à la fois à partir d'une usine partagée sans effets secondaires ni problèmes.

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

Ainsi, par exemple, vous devriez pouvoir créer une seule instance de DocumentBuilderFactory via DocumentBuilderFactory.newInstance, puis utiliser cette seule fabrique pour créer un DocumentBuilder par thread via DocumentBuilderFactory.newDocumentBuilder. Vous pouvez également créer un pool de DocumentBuilders.

Je ne trouve aucun endroit qui indique que, par exemple, la méthode statique DocumentBuilderFactory.newInstance est thread-safe. L'implémentation semble thread-safe dans la mesure où une synchronisation de méthode est effectuée, mais la spécification indique spécifiquement que DocumentBuilderFactory.newDocumentBuilder est thread-safe.

16
ttt

Vous devez savoir trois choses:

  1. Quel est le coût de création de l'usine? Si le coût est faible, votre gain de performances pourrait être proche de zéro.
  2. Quel est le coût de création du constructeur? Si le coût est faible, votre gain de performances pourrait être proche de zéro.
  3. Le fil d'usine et/ou de constructeur est-il sûr? Sinon, vous devez vous assurer que la méthode qui y accède est sécurisée pour les threads à l'aide du mot clé synchronized.

Je ne connais pas les classes DocumentBuilder que vous utilisez, mais toutes ces informations devraient être disponibles dans sa documentation javadoc ou autre. Si la création de certains objets coûte cher, ils vous jettent généralement ces informations.

2
Rasmus Franke