Nashorn:具有单例ScriptEngine的并发eval?线程安全吗?

4
我们希望在servlet中使用Nashorn。想法是使用ScriptEngine的单例实例,该实例在每个请求中被重复使用。每次请求时,都会创建一个新的EngineScope Binding,并使用该绑定运行eval。然后清除绑定。没有共享对象传递给绑定(仅传递来自servlet的请求/响应对象)。
在servlet内部,ScriptEngine的单例实例可以在不同的线程中并发地进行eval运算,这样做是否正常工作或者会遇到线程问题?以下是一些代码,以便理解:
ScriptEngine engine = getNashornSingleton();

ScriptContext newContext = new SimpleScriptContext();
newContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
Bindings engineScope =newContext.getBindings(ScriptContext.ENGINE_SCOPE);

engineScope.put("request", request);
engineScope.put("response", response);

engine.eval(jsCode, engineScope);
engineScope.clear();
1个回答

4

对于我自己的问题,我的答案是:我不会使用上述所描述的单例模式。尽管存在一些潜在的线程问题,但你可能不希望在每个请求中销毁绑定(因为这可能需要重新编译脚本)。我们最终采取的做法是创建一个引擎池及其关联的作用域绑定。即一个引擎/绑定对。

在每个Servlet请求中,我们从池中获取一个引擎/绑定对,将请求/响应放入绑定中,然后执行脚本。不必担心线程问题,因为给定的引擎/绑定对一次只能由一个线程执行。当请求完成时,引擎/绑定对将返回到池中。看起来效果不错。


我想知道您的设置是否能提供更详细的信息。我们在使用Nashorn进行服务器端渲染时(使用React),遇到了类似的并发问题。我们尝试将引擎包装在ThreadLocal中,但性能非常差。现在我们正在尝试使用CompiledScript和Invocable。您的线程池有多大?如果请求数等于线程池大小,那么您可能会遇到问题... - mryan
我选择了一组Nashorn引擎。我的第一次尝试是只有一组引擎,并在每次重新创建新的绑定(但这似乎强制要求每次调用脚本时重新编译Nashorn脚本)。因此,我创建了一个包装对象来保留引擎及其相关联的绑定。这不需要在servlet调用相同脚本时重新编译脚本。一旦servlet完成,包装对象将返回到池中。 - adamM
每隔一段时间就会调用一个收割线程来销毁未使用的引擎/绑定。性能足够好,比普通servlet略慢,但仍然很好。有许多关于如何从文件加载脚本而无需重新编译的小细节。我正在考虑开源它,你觉得怎么样? - adamM
Nashorn脚本不在单独的线程中运行,而是在与servlet线程相同的线程中运行。因此,无需担心每个请求都会添加更多线程。该池不是线程池,而是可重用的Nashorn引擎/绑定。 - adamM
关于池大小,它是动态增长的。如果池中没有空闲引擎,它会创建一个新的引擎并将其添加到池中。池的大小可能会变得很大,但这并不重要。一旦负载减少,它们最终会被回收线程收集起来。 - adamM
显示剩余5条评论

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