我应该为每个线程使用单独的 ScriptEngine 和 CompiledScript 实例吗?

Should I use a separate ScriptEngine and CompiledScript instances per each thread?

我的程序使用 Java 脚本 API 并且可以同时评估一些脚本。它们不使用共享脚本对象、绑定或上下文,但可以使用相同的 ScriptEngineCompiledScript 对象。我看到 Java 8 中的 Oracle Nashorn 实现不是多线程的,ScriptEngineFactory.getParameter('THREADING') returns null 文档说:

The engine implementation is not thread safe, and cannot be used to execute scripts concurrently on multiple threads.

这是否意味着我应该为每个线程创建一个单独的 ScriptEngine 实例? 此外,文档没有提到 CompiledScript 并发使用但是:

Each CompiledScript is associated with a ScriptEngine

可以假定 CompiledScript 线程安全依赖于相关的 ScriptEngine,即我应该使用 Nashorn 为每个线程使用单独的 CompiledScript 实例。

如果我应该,对于这种(我认为很常见的)情况,使用 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();
}

您可以跨线程共享 ScriptEngineCompiledScript 对象。它们是线程安全的。实际上,您应该共享它们,因为单个引擎实例是 class 缓存和 JavaScript 对象的隐藏 classes 的持有者,因此只有一个引擎实例可以减少重复编译.

您不能共享的是 Bindings 个对象。绑定对象基本上对应于 JavaScript 运行时环境的 Global 对象。引擎以默认绑定实例开始,但如果您在多线程环境中使用它,则需要使用 engine.createBindings() 为每个线程获取一个单独的绑定对象——它自己的全局对象,并将已编译的脚本计算到其中。这样您就可以使用相同的代码设置隔离的全局范围。 (当然,您也可以将它们组合在一起,或者对它们进行同步,只要确保在一个绑定实例中工作的线程不超过一个即可)。将脚本评估为绑定后,您随后可以有效地调用它用 ((JSObject)bindings.get(fnName).call(this, args...)

定义的函数

如果必须跨线程共享状态,那么至少要尽量让它不可变。如果您的对象是不可变的,您不妨将脚本评估为单个 Bindings 实例,然后跨线程使用它(希望调用无副作用的函数)。如果它是可变的,你就必须同步;整个绑定,或者您也可以使用 var syncFn = Java.synchronized(fn, lockObj) Nashorn 特定的 JS API 来获取在特定对象上同步的 JS 函数版本。

这假定您跨线程共享单个绑定。如果你想让多个绑定共享对象的一个​​子集(例如,将同一个对象放入多个绑定中),同样,你将不得不以某种方式处理确保自己对共享对象的访问是线程安全的。

至于THREADING parameter returning null:是的,最初我们计划不让引擎线程安全(也就是说语言本身不是线程安全的)所以我们选择了空值。我们现在可能需要重新评估它,因为与此同时我们确实做到了,因此引擎实例是线程安全的,只是全局范围(绑定)不是(并且永远不会,因为 JavaScript 语言语义.)

Nashorn 的

ScriptEngine 不是线程安全的。这可以通过为 Nashorn 调用 ScriptEngineFactoryScriptEngineFactory.getParameter("THREADING") 来验证。

返回的值为 null,根据 java doc 表示不是线程安全的。

注:这部分答案是here先给出的。但是我重新检查了结果并自己记录了。

这也为我们提供了 CompiledScript 的答案。根据 java doc,一个 CompiledScript 与一个 ScriptEngine 相关联。

所以在Nashorn中ScriptEngineCompiledScript不应该被两个线程同时使用.

@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();
    
    <pre><code>    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,它似乎对我的用例表现良好。

接受的答案会误导很多人。

简而言之:

  • NashornScriptEngine 不是线程安全的
  • 如果您使用全局绑定,无状态部分可以线程安全