Должен ли я использовать отдельные экземпляры 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

  1. мой код js что-то вроде этого:

    
    var renderServer = function renderServer(server_data) {
       //your js logic...
       return html_string.
    }
    
  2. 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 является потокобезопасным, и проблемы с безопасностью потоков возникают, когда сценарий отличается.

Другие вопросы по тегам