我的程序使用Java Scripting API,并且可以同时评估某些脚本。他们不使用共享脚本对象,绑定或上下文,但可以使用ScriptEngine和CompiledScript对象。我看到,在Java的甲骨文8实施犀牛不是多线程的,ScriptEngineFactory.getParameter('THREADING')回报率null约该文件说:
ScriptEngine
CompiledScript
ScriptEngineFactory.getParameter('THREADING')
null
引擎实现不是线程安全的,并且不能用于在多个线程上同时执行脚本。
这是否意味着我应该ScriptEngine为每个线程创建一个单独的实例?此外,文档没有说明CompiledScript并发使用,而是:
每个CompiledScript与一个ScriptEngine相关联
可以假设CompiledScript线程安全性取决于related ScriptEngine,即我应该CompiledScript为每个带有Nashorn的线程使用单独的实例。
如果可以的话,使用ThreadLocal,池或其他方法来解决这种情况(我认为很常见)是什么合适的解决方案?
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(); }
您可以跨线程共享ScriptEngine和CompiledScript对象。它们是线程安全的。实际上,您应该共享它们,因为单个引擎实例是类缓存和JavaScript对象的隐藏类的持有者,因此,只有一个实例可以减少重复编译。
您无法共享的是Bindings对象。绑定对象基本上对应于JavaScript运行时环境的Global对象。该引擎以默认的绑定实例开始,但是如果在多线程环境中使用它,则需要使用它engine.createBindings()为每个线程(它自己的全局变量)获取一个单独的Bindings对象,并评估其中的编译脚本。这样,您将使用相同的代码设置隔离的全局范围。(当然,您也可以合并它们,或在’em上进行同步,只需确保在一个绑定实例中工作的线程不超过一个)。将脚本评估为绑定之后,您可以随后有效地调用其定义的函数((JSObject)bindings.get(fnName).call(this, args...)
Bindings
Global
engine.createBindings()
((JSObject)bindings.get(fnName).call(this, args...)
如果必须在线程之间共享状态,则至少尝试使其不可变。如果您的对象是不可变的,则最好将脚本评估为单个Bindings实例,然后跨线程使用它(希望调用无副作用的函数)。如果是可变的,则必须进行同步;既可以是整个绑定,也可以使用var syncFn = Java.synchronized(fn, lockObj)Nashorn特定的JS API来获取在特定对象上同步的JS函数的版本。
var syncFn = Java.synchronized(fn, lockObj)
前提是您在线程之间共享单个绑定。如果要让多个绑定共享对象的子集(例如,通过将同一个对象放入多个绑定中),则必须以某种方式处理确保对共享对象的访问本身是线程安全的。
至于THREADING返回null的参数:是的,最初我们计划不使引擎具有线程安全性(假设语言本身不是线程安全的),所以我们选择了null值。我们现在可能需要重新评估,因为与此同时我们做到了,以便引擎实例是线程安全的,只是全局范围(绑定)不是(并且永远不会,因为JavaScript语言语义)。
THREADING