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();
}
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.)
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 .
La réponse acceptée induira en erreur de nombreuses personnes.
En bref:
NashornScriptEngine
est PAS thread-safeExemple de code pour la réponse de @ attilla
mon code js est quelque chose comme ceci:
var renderServer = function renderServer(server_data) {
//your js logic...
return html_string.
}
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.