【问题标题】:Should I use a separate ScriptEngine and CompiledScript instances per each thread?我应该为每个线程使用单独的 ScriptEngine 和 CompiledScript 实例吗?
【发布时间】:2015-07-20 07:55:33
【问题描述】:

我的程序使用 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();
}

【问题讨论】:

    标签: java multithreading nashorn jsr223


    【解决方案1】:

    您可以跨线程共享ScriptEngineCompiledScript 对象。它们是线程安全的。实际上,您应该共享它们,因为单个引擎实例是类缓存和 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 parameter returning null:是的,最初我们计划不让引擎线程安全(说语言本身不是线程安全的)所以我们选择了空值。我们现在可能需要重新评估这一点,因为在此期间我们确实做到了,引擎实例是线程安全的,只是全局范围(绑定)不是(而且永远不会是,因为 JavaScript 语言语义。)

    【讨论】:

    • 我唯一的问题是关于绑定对象到 JSObject 的转换。 Eclipse 对此抱怨:对所需库“/Library/Java/JavaVirtualMachines/jdk1.8.0_71.jdk/Contents/Home/jre/lib/ext/nashorn.jar”的限制。这是为什么呢?
    • 是否有代码示例展示了如何在多线程环境中正确使用 Nashorn?另外,我在ScriptEngine 上找不到newBindings() 方法-应该是createBindings()
    • @Szymon 您是否有幸找到了一些代码示例或 newBindings 在哪里?
    • @dyesdyes 不,经过一些初步研究,我决定不在项目中使用 Nashorn,所以我停止了进一步调查。
    • 在使用 One JSEngine 服务所有线程时面临严重的串扰问题。尽管在每次执行之前都复制了绑定(使用 putAll ),但 JMeter 测试证明对整体结果是致命的。最终改为使用线程本地引擎。
    【解决方案2】:

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

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

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

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

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

    【讨论】:

    • 这与 Attila 更全面的回答相矛盾。鉴于他的背景和所提供的上下文,我认为这是正确的。
    【解决方案3】:

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

    简而言之:

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

    【讨论】:

    • 您能详细说明“可以”吗?如果代码只是函数(无状态),那么它是线程安全的,还是需要做更多的工作?
    【解决方案4】:

    @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,它似乎在我的用例中表现良好。

    【讨论】:

    • 在您的 js 代码中,如果“您的 js 逻辑”没有任何状态,例如不引用任何全局变量,它不是线程安全的吗?即如果js代码中没有状态,是否需要池等?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-11
    • 1970-01-01
    • 2021-02-07
    • 2018-03-20
    • 1970-01-01
    • 2018-04-05
    相关资源
    最近更新 更多