Должен ли я использовать отдельные экземпляры ScriptEngine и CompiledScript для каждого потока?
Моя программа использует Java Scripting API и может анализировать некоторые скрипты одновременно. Они не используют общие объекты script, привязки или контекст, но могут использовать те же объекты ScriptEngine
и CompiledScript
. Я вижу, что реализация Oracle Nashorn в Java 8 не является многопоточной, ScriptEngineFactory.getParameter('THREADING')
возвращает null
, о которой говорится в документации:
Реализация ядра не является потокобезопасной и не может использоваться для выполнять скрипты одновременно на нескольких потоках.
Означает ли это, что я должен создать отдельный экземпляр ScriptEngine
для каждого потока?
Кроме того, документация ничего не говорит о параллельном использовании CompiledScript
, но:
Каждый CompiledScript связан с ScriptEngine
Можно предположить, что CompiledScript
безопасность потоков зависит от связанного ScriptEngine
, т.е. я должен использовать отдельный экземпляр CompiledScript
для каждого потока с помощью Nashorn.
Если мне нужно, какое подходящее решение для этого (я думаю, очень распространенный) случай, используя ThreadLocal
, пул или что-то еще?
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();
}
Ответы
Ответ 1
Вы можете делиться объектами ScriptEngine
и CompiledScript
по потокам. Они потокобезопасны. Фактически, вы должны делиться ими, поскольку один экземпляр ядра является держателем для кеша класса и скрытых классов объектов JavaScript, поэтому, имея только один, вы сокращаете повторную компиляцию.
То, что вы не можете использовать, - это объекты Bindings
. Объект bindings в основном соответствует объекту среды Global
среды выполнения JavaScript. Двигатель запускается с экземпляром привязки по умолчанию, но если вы используете его в многопоточной среде, вам нужно использовать engine.newBindings()
для получения отдельного объекта Bindings для каждого потока - своего собственного глобального и оценить скомпилированные скрипты в нем. Таким образом вы создадите изолированные глобальные области с тем же кодом. (Конечно, вы также можете объединить их или синхронизировать с ними, просто убедитесь, что в одном экземпляре привязки не более одного потока). После того как вы оценили script в привязках, вы можете впоследствии эффективно вызывать функции, определенные им с помощью ((JSObject)bindings.get(fnName).call(this, args...)
Если вы должны совместно использовать состояние по потокам, по крайней мере, попробуйте сделать его не изменчивым. Если ваши объекты неизменяемы, вы можете также оценить script в одном экземпляре Bindings
, а затем просто использовать это в потоках (вызывая надежные побочные эффекты). Если он изменен, вам придется синхронизировать; либо целые привязки, либо вы можете использовать специфический JS-API var syncFn = Java.synchronized(fn, lockObj)
Nashorn для получения версий функций JS, которые синхронизируются с определенным объектом.
Это предполагает, что вы используете отдельные привязки по потокам. Если вы хотите иметь несколько привязок, совместно использующих подмножество объектов (например, поместив один и тот же объект в несколько привязок), снова вам придется как-то справиться с тем, чтобы обеспечить доступ к общему объекту самостоятельно.
Что касается параметра THREADING
, возвращающего нуль: да, изначально мы планировали не создавать потоки в потоковом режиме (говоря, что сам язык не является потокобезопасным), поэтому мы выбрал нулевое значение. Возможно, нам придется переоценить это сейчас, так как в то же время мы сделали это так, чтобы экземпляры ядра были потокобезопасными, а глобальная область (привязки) не является (и никогда не будет, из-за семантики языка JavaScript).
Ответ 2
ScriptEngine для Nashorn не является потокобезопасным. Это можно проверить, вызвав ScriptEngineFactory.getParameter( "РЕЗЬБА" ) ScriptEngineFactory для Nashorn.
Возвращаемое значение равно null, которое согласно java doc означает, что он не является безопасным для потоков.
Примечание. Эта часть ответа была впервые предоставлена здесь. Но я сам перепроверял результаты и документ.
Это дает нам ответ для CompiledScript. Согласно java doc, CompiledScript связан с одним ScriptEngine.
Итак, в Nashorn ScriptEngine и CompiledScript не должны использоваться двумя потоками одновременно.
Ответ 3
Принятый ответ обманет многих людей.
Короче:
-
NashornScriptEngine
НЕ поточно-безопасный
- Если вы используете НЕ глобальные привязки, то без учета состояния может быть потокобезопасным
Ответ 4
Пример кода для ответа @attilla
-
Мой код js выглядит примерно так:
var renderServer = function renderServer(server_data) {
//your js logic...
return html_string.
}
-
Код 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();
}
}
Код>
Будьте осторожны при использовании метода renderServer
в многопоточной среде, поскольку привязки не являются потокобезопасными. Одним из решений является использование нескольких экземпляров renderServer
с повторно используемыми пулами объектов. Я использую org.apache.commons.pool2.impl.SoftReferenceObjectPool
, который, кажется, хорошо работает для моего использования.