Следует ли использовать отдельные экземпляры ScriptEngine и CompiledScript для каждого потока?
Проблема с многопоточностью при использовании Java Scripting API и Nashorn:
Я использую Java Scripting API в своей программе и могу одновременно выполнять некоторые скрипты. Скрипты не используют общие объекты скриптов,Bindings или Context, но могут использовать одни и те же объекты ScriptEngine
и CompiledScript
. Я заметил, что реализация Nashorn от Oracle в 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()); // Безопасен ли этот код для потоков?
compiled.eval(new SimpleBindings()); // А этот?
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
threads.start();
}
Буду признателен за любые советы или рекомендации по решению этой проблемы.
3 ответ(ов)
Ответ, который принят, может ввести в заблуждение многих пользователей.
Вкратце:
NashornScriptEngine
НЕ является потокобезопасным.- Если вы НЕ используете глобальные биндинги, то безсостояние (stateless) часть может быть потокобезопасной.
Если вы хотите получить код на JavaScript и Java для вызова функции renderServer
, а также советы по использованию в многопоточной среде, вот пример, основанный на вашем вопросе.
Пример кода
- JavaScript код:
var renderServer = function renderServer(server_data) {
// Ваша логика на JavaScript...
return html_string; // здесь должен быть возвращаемый HTML
};
- Java код:
public static void main(String[] args) {
String jsFilePath = jsFilePath(); // Путь к вашему JavaScript файлу
String jsonData = jsonData(); // Данные в формате JSON
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
в многопоточной среде требует осторожности, так как привязки (bindings) не являются потокобезопасными. Одним из решений может быть использование нескольких экземпляров renderServer
с переиспользуемыми пулами объектов. В моем случае я использую org.apache.commons.pool2.impl.SoftReferenceObjectPool
, который, кажется, хорошо работает для моего случая.
Если у вас есть дополнительные вопросы или требуется помощь в дальнейшем, не стесняйтесь спрашивать!
Результат моих попыток таков: выполнение одного и того же скрипта в виде строки с использованием ScriptEngine является потокобезопасным, однако проблемы с потокобезопасностью возникают, когда скрипты разные.
Что значит 'synchronized'?
Разница между интерфейсами Runnable и Callable в Java
Как распределяются потоки для обработки запросов Servlet?
Что делает SwingUtilities.invokeLater?
SwingUtilities.invokeLater: Вызов кода в потоке событий Swing