Подтвердить что ты не робот

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

Ответ 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, который, кажется, хорошо работает для моего использования.