web-dev-qa-db-fra.com

Dois-je utiliser des instances ScriptEngine et CompiledScript distinctes pour chaque thread?

Mon programme utilise Java API de script et peut évaluer certains scripts simultanément. Ils n'utilisent pas d'objets de script partagés, de liaisons ou de contexte, mais peuvent utiliser les mêmes ScriptEngine et CompiledScript objets. Je vois que l'implémentation d'Oracle Nashorn dans Java 8 n'est pas multithread, ScriptEngineFactory.getParameter('THREADING') renvoie null à propos de laquelle la documentation dit:

L'implémentation du moteur n'est pas sécurisée pour les threads et ne peut pas être utilisée pour exécuter des scripts simultanément sur plusieurs threads.

Cela signifie-t-il que je devrais créer une instance distincte de ScriptEngine pour chaque thread? De plus, la documentation ne dit rien sur l'utilisation simultanée de CompiledScript mais:

Chaque CompiledScript est associé à un ScriptEngine

On peut supposer que CompiledScript la sécurité des threads dépend de ScriptEngine, c'est-à-dire que je devrais utiliser une instance CompiledScript distincte pour chaque thread avec Nashorn.

Si je devais, quelle est la solution appropriée pour ce cas (je pense très commun), en utilisant ThreadLocal, un pool ou autre chose?

final String script = "...";
final CompiledScript compiled = ((Compilable)scriptEngine).compile(script);
for (int i=0; i<50; i++) {
    Thread thread = new Thread () {
        public void run() {
            try {
                scriptEngine.eval(script, new SimpleBindings ());  //is this code thread-safe?
                compiled.eval(new SimpleBindings ());  //and this?
            }
            catch (Exception e)  {  throw new RuntimeException (e);  }
        }
    };
    threads.start();
}
51
And390

Vous pouvez partager des objets ScriptEngine et CompiledScript entre plusieurs threads. Ils sont threadsafe. En fait, vous devez les partager, car une seule instance de moteur est un support pour un cache de classe et pour les classes cachées des objets JavaScript, donc en n'en ayant qu'une, vous réduisez la compilation répétée.

Ce que vous ne pouvez pas partager, ce sont les objets Bindings. L'objet bindings correspond essentiellement à l'objet Global de l'environnement d'exécution JavaScript. Le moteur démarre avec une instance de liaisons par défaut, mais si vous l'utilisez dans un environnement multithread, vous devez utiliser engine.createBindings() pour obtenir un objet Bindings distinct pour chaque thread - son propre global, et évaluer les scripts compilés dans il. De cette façon, vous configurerez des étendues globales isolées avec le même code. (Bien sûr, vous pouvez également les regrouper ou les synchroniser, assurez-vous simplement qu'il n'y a jamais plus d'un thread qui fonctionne dans une instance de liaisons). Une fois que vous avez évalué le script dans les liaisons, vous pouvez ensuite invoquer efficacement les fonctions qu'il a définies avec ((JSObject)bindings.get(fnName).call(this, args...)

Si vous devez partager l'état entre les threads, essayez au moins de le rendre non modifiable. Si vos objets sont immuables, vous pourriez tout aussi bien évaluer le script en une seule instance Bindings et ensuite simplement l'utiliser sur plusieurs threads (en invoquant, espérons-le, des fonctions sans effets secondaires). S'il est modifiable, vous devrez vous synchroniser; soit l'ensemble des liaisons, soit vous pouvez également utiliser l'API JS var syncFn = Java.synchronized(fn, lockObj) spécifique à Nashorn pour obtenir des versions des fonctions JS qui se synchronisent sur un objet spécifique.

Cela suppose que vous partagiez des liaisons uniques entre les threads. Si vous souhaitez que plusieurs liaisons partagent un sous-ensemble d'objets (par exemple, en plaçant le même objet dans plusieurs liaisons), encore une fois, vous devrez vous assurer que l'accès aux objets partagés est thread-safe vous-même.

Quant à THREADING paramètre retournant null : oui, au départ, nous avions prévu de ne pas rendre le threadsafe du moteur (en disant que la langue elle-même n'est pas threadsafe), nous avons donc choisi la valeur null. Nous devrons peut-être réévaluer cela maintenant, comme entre-temps nous avons fait en sorte que les instances de moteur soient threadsafe, juste la portée globale (liaisons) n'est pas (et ne le sera jamais, en raison de la sémantique du langage JavaScript.)

57
Attila Szegedi

ScriptEngine pour Nashorn n'est pas thread-safe. Cela peut être vérifié en appelant la ScriptEngineFactory.getParameter("THREADING") de la ScriptEngineFactory pour Nashorn.

La valeur renvoyée est null ce qui, selon Java doc signifie pas thread-safe.

Remarque: Cette partie de la réponse a d'abord été donnée ici . Mais j'ai revérifié les résultats et doc moi-même.

Cela nous donne également la réponse pour CompiledScript. Selon Java doc un CompiledScript est associé à un ScriptEngine.

Donc, dans Nashorn ScriptEngine et CompiledScript ne doivent pas être utilisés par deux threads en même temps .

5
Thim Anneessens

La réponse acceptée induira en erreur de nombreuses personnes.

En bref:

  • NashornScriptEngine est PAS thread-safe
  • Si vous utilisez PAS utilisez des liaisons globales, la partie sans état peut être thread-safe
0
shawn

Exemple de code pour la réponse de @ attilla

  1. mon code js est quelque chose comme ceci:

    
    var renderServer = function renderServer(server_data) {
       //your js logic...
       return html_string.
    }
    
  2. Code Java:

    
    public static void main(String[] args) {
            String jsFilePath = jsFilePath();
            String jsonData = jsonData();
    
    
        try (InputStreamReader isr = new InputStreamReader(new URL(jsFilePath).openStream())) {
    
            NashornScriptEngine engine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn");
            CompiledScript compiledScript = engine.compile(isr);
            Bindings bindings = engine.createBindings();
    
            compiledScript.eval(bindings);
    
            ScriptObjectMirror renderServer = (ScriptObjectMirror) bindings.get("renderServer");
            String html = (String) renderServer.call(null, jsonData);
            System.out.println(html);
    
       } catch (Exception e) {
           e.printStackTrace();
       }
    }
    </code>

soyez prudent lorsque vous utilisez la méthode renderServer dans un environnement multithread, car les liaisons ne sont pas thread-safe. Une solution consiste à utiliser plusieurs instances de renderServer avec des pools d'objets réutilisables. J'utilise org.Apache.commons.pool2.impl.SoftReferenceObjectPool, qui semble bien fonctionner pour mon cas d'utilisation.

0
mt.uulu