每个线程是否应该使用单独的ScriptEngine和CompiledScript实例?

55

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

61
您可以在不同线程之间共享ScriptEngineCompiledScript对象,它们是线程安全的。实际上,您应该分享它们,因为单个引擎实例是类缓存和JavaScript对象隐藏类的持有者,因此只要有一个,就可以减少重复编译。
但是,您不能共享Bindings对象。绑定对象基本上对应于JavaScript运行时环境的Global对象。引擎从默认绑定实例开始,但如果您在多线程环境中使用它,则需要使用engine.createBindings()为每个线程获取单独的绑定对象--自己的全局,并将编译的脚本评估到其中。这样,您将设置具有相同代码的隔离全局范围。(当然,您还可以对它们进行池化或同步,只需确保永远没有多个线程在一个绑定实例中工作即可)。一旦您将脚本评估到绑定中,就可以随后有效地使用((JSObject)bindings.get(fnName).call(this, args...))来调用其定义的函数。
如果您必须在线程之间共享状态,那么至少尝试使其不可变。如果您的对象是不可变的,那么您可以将脚本评估为单个Bindings实例,然后在线程之间仅使用该实例(调用希望没有副作用的函数)。如果它是可变的,则必须进行同步;要么同步整个绑定,要么还可以使用var syncFn = Java.synchronized(fn, lockObj) Nashorn特定JS API来获得在特定对象上同步的JS函数版本。
这预设您在线程之间共享单个绑定。如果您想让多个绑定共享一组对象(例如将相同对象放入多个绑定中),则需要自己处理确保对共享对象的访问是线程安全的问题。

THREADING参数返回null:最初我们计划不使引擎线程安全(因为该语言本身并非线程安全),因此选择了null值。现在我们可能需要重新评估这一决策,因为与此同时我们确实使引擎实例线程安全了,只是全局范围(绑定)不是(也永远不会是,因为JavaScript语言本身的语义问题)。


我唯一的问题是关于将bindings对象转换为JSObject。Eclipse会抱怨这个:对所需库'/Library/Java/JavaVirtualMachines/jdk1.8.0_71.jdk/Contents/Home/jre/lib/ext/nashorn.jar'的限制。为什么会这样? - Gregor
2
有没有代码示例展示如何在多线程环境下正确使用Nashorn?此外,我找不到ScriptEngine上的newBindings()方法 - 应该是createBindings()吗? - Szymon
@dyesdyes 不,经过一些初步的研究,我决定不在项目中使用Nashorn,所以我停止了进一步的调查。 - Szymon
3
在使用一种JSEngine为所有线程提供服务时,遇到了严重的串扰问题。虽然在每次执行之前都复制了绑定(使用putAll),但使用JMeter测试后发现对整体结果产生致命影响。最终采用了基于线程本地变量的引擎解决了问题。 - NishM
我尝试过使用非编译(只是Java字符串作为脚本)的脚本,目前看起来没有问题。没有交叉对话。有人尝试过这个吗? - faizan
显示剩余3条评论

4

NashornScriptEngine不是线程安全的。这可以通过调用ScriptEngineFactorygetParameter("THREADING")方法进行验证。

返回值为null,根据Java文档的说明意味着不是线程安全的。

注意:此部分回答最初在此处给出。但我自己重新检查了结果和文档。

这也适用于CompiledScript。根据Java文档的说明,一个CompiledScript与一个ScriptEngine相关联。

因此,在Nashorn中,不应该同时使用两个线程使用ScriptEngineCompiledScript


12
这与Attila更全面的回答相矛盾。考虑到他的背景和提供的上下文,我认为这是正确的回答。 - Douglas Drouillard

1

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

简而言之:

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

你能详细说明一下“可以”吗?如果代码只是函数(没有状态),那么它是否是线程安全的,还需要做更多的工作吗? - John Little

0

@attilla回答的代码示例

我的JavaScript代码大致如下:
var renderServer = function renderServer(server_data) { //你的js逻辑... 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,它似乎在我的用例中表现良好。


在您的JS代码中,如果“您的JS逻辑”没有任何状态,例如不引用任何全局变量,那么它是否是线程安全的?也就是说,如果JS代码中没有状态,是否需要使用池等工具? - John Little

0
我的尝试结果是,执行相同的字符串脚本时,ScriptEngine是线程安全的,而当脚本不同时,就会出现线程安全问题。

1
你的回答可以通过提供更多支持性信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人能够确认你的回答是否正确。你可以在帮助中心找到关于如何撰写好回答的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接