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

时间:2015-05-09 12:47:07

标签: java multithreading nashorn jsr223

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

  

引擎实现不是线程安全的,不能用于   在多个线程上并发执行脚本。

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

  

每个CompiledScript都与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();
}

4 个答案:

答案 0 :(得分:54)

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

您无法分享的是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 parameter returning null:是的,最初我们计划不使引擎线程安全(说语言本身不是线程安全的),所以我们选择了null值。我们现在可能需要重新评估,因为在此期间我们确实使得引擎实例是线程安全的,只是全局范围(绑定)不是(并且永远不会,因为JavaScript语言语义。)

答案 1 :(得分:5)

Nashorn的

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

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

注意:答案的这一部分首先给出here。但我自己重新检查结果和文档。

这也为我们提供了ScriptEngineFactory的答案。根据{{​​3}},CompiledScript与一个CompiledScript相关联。

所以在Nashorn ScriptEngineScriptEngine不应该同时由两个线程使用

答案 2 :(得分:0)

@ attilla回复的代码示例

  1. 我的js代码是这样的:

    s1 + s2 + 1m
  2. java代码:

    
    var renderServer = function renderServer(server_data) {
       //your js logic...
       return html_string.
    }
    
  3. 在多线程环境中使用 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();
       }
    }
    
    方法时要小心,因为绑定不是线程安全的。一种解决方案是使用具有可重用对象池的多个 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,这似乎对我的用例表现良好。

答案 3 :(得分:0)

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

简而言之:

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