Должен ли я использовать отдельные экземпляры ScriptEngine и CompiledScript для каждого потока?
Моя программа использует Java Scripting API и может одновременно оценивать некоторые сценарии. Они не используют общие объекты сценария, привязки или контекст, но могут использовать те же 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();
}
5 ответов
Вы можете поделиться ScriptEngine
а также CompiledScript
объекты через потоки. Они безопасны. На самом деле, вы должны поделиться ими, так как один экземпляр движка является держателем для кэша классов и для скрытых классов объектов JavaScript, поэтому, имея только один, вы сокращаете повторную компиляцию.
То, что вы не можете поделиться это Bindings
объекты. Объект bindings в основном соответствует среде выполнения JavaScript Global
объект. Движок запускается с экземпляром привязок по умолчанию, но если вы используете его в многопоточной среде, вам нужно использовать engine.createBindings()
получить отдельный объект Bindings для каждого потока - его собственный глобальный объект и оценить скомпилированные сценарии в него. Таким образом, вы будете устанавливать изолированные глобальные области с одним и тем же кодом. (Конечно, вы также можете объединить их или синхронизировать их, просто убедитесь, что в одном экземпляре привязок никогда не работает более одного потока). Как только вы оценили скрипт в привязках, вы можете впоследствии эффективно вызывать функции, которые он определил с помощью ((JSObject)bindings.get(fnName).call(this, args...)
Если вы должны разделить состояние между потоками, то хотя бы попытайтесь сделать его не изменяемым. Если ваши объекты неизменны, вы можете также оценить сценарий в один Bindings
экземпляр, а затем просто использовать это в потоках (вызывая, мы надеемся, свободные от побочных эффектов функции). Если он изменчив, вам придется синхронизироваться; либо целые привязки, или вы также можете использовать var syncFn = Java.synchronized(fn, lockObj)
Специфичный для Nashorn JS API для получения версий функций JS, которые синхронизируются с конкретным объектом.
Это предполагает, что вы разделяете одиночные привязки между потоками. Если вы хотите, чтобы несколько привязок совместно использовали подмножество объектов (например, помещая один и тот же объект в несколько привязок), вам снова придется каким-то образом убедиться в том, что доступ к совместно используемым объектам является потокобезопасным самостоятельно.
Что касается THREADING
параметр, возвращающий нуль: да, изначально мы планировали не делать движок безопасным от потоков (говоря, что сам язык не безопасен для потоков), поэтому мы выбрали нулевое значение. Возможно, нам придется пересмотреть это сейчас, так как в то же время мы сделали так, чтобы экземпляры движка были потокобезопасными, просто глобальная область (привязки) не является (и никогда не будет из-за семантики языка JavaScript).
ScriptEngine
для Nashorn не является потокобезопасным. Это можно проверить, позвонив в ScriptEngineFactory.getParameter("THREADING")
из ScriptEngineFactory
для Нашорна.
Возвращаемое значение равно нулю, что в соответствии с Java документом означает не потокобезопасным.
Примечание: эта часть ответа была впервые дана здесь. Но я перепроверил результаты и документировал сам.
Это дает нам ответ на CompiledScript
также. По словам Ява Док CompiledScript
связан с одним ScriptEngine
,
Так в Нашорне ScriptEngine
а также CompiledScript
не должны использоваться двумя потоками одновременно.
Принятый ответ введет в заблуждение многих людей.
Короче:
NashornScriptEngine
НЕ является потокобезопасным- Если вы НЕ используете глобальные привязки, часть без состояния может быть поточно-ориентированной
Пример кода для ответа @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
, который, кажется, хорошо работает для моего случая использования.
Результатом моей попытки является то, что при выполнении одного и того же сценария String ScriptEngine является потокобезопасным, и проблемы с безопасностью потоков возникают, когда сценарий отличается.