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

时间:2021-01-24 06:53:43

My program uses Java Scripting API and can eval some scripts concurrently. They don't use shared script objects, Bindings or Context, but can use same ScriptEngine and CompiledScript objects. I see that Oracle Nashorn implementation in Java 8 is not multithreaded, ScriptEngineFactory.getParameter('THREADING') returns null about which the documentation says:

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

The engine implementation is not thread safe, and cannot be used to execute scripts concurrently on multiple threads.

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

Does it mean that I should create a separate instance of ScriptEngine for each thread? Besides, documentation says nothing about CompiledScript concurrent usage but:

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

Each CompiledScript is associated with a ScriptEngine

每个CompiledScript都与ScriptEngine相关联

It can be assumed that CompiledScript thread-safety depends on related ScriptEngine, i.e. I should use separate CompiledScript instance for each thread with Nashorn.

可以假设CompiledScript线程安全依赖于相关的ScriptEngine,即我应该为Nashorn的每个线程使用单独的CompiledScript实例。

If I should, what is the appropriate solution for this (I think very common) case, using ThreadLocal, a pool or something else?

如果我应该,对于这个(我认为非常常见的)情况,使用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 个解决方案

#1


50  

You can share a ScriptEngine and CompiledScript objects across threads. They are threadsafe. Actually, you should share them, as a single engine instance is a holder for a class cache and for JavaScript objects' hidden classes, so by having only one you cut down on repeated compilation.

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

What you can't share is Bindings objects. The bindings object basically corresponds to the JavaScript runtime environment's Global object. The engine starts with a default bindings instance, but if you use it in multithreaded environment, you need to use engine.createBindings() to obtain a separate Bindings object for every thread -- its own global, and evaluate the compiled scripts into it. That way you'll set up isolated global scopes with the same code. (Of course, you can also pool them, or synchronize on 'em, just make sure there's never more than one thread working in one bindings instance). Once you evaluated the script into the bindings, you can subsequently efficiently invoke functions it defined with ((JSObject)bindings.get(fnName).call(this, args...)

你不能分享的是Bindings对象。绑定对象基本上对应于JavaScript运行时环境的Global对象。引擎以默认绑定实例启动,但如果在多线程环境中使用它,则需要使用engine.createBindings()为每个线程获取单独的Bindings对象 - 它自己的全局,并将编译后的脚本评估到其中。这样,您将使用相同的代码设置隔离的全局范围。 (当然,您也可以对它们进行池化,或者在它们上进行同步,只需确保在一个绑定实例中永远不会有多个线程工作)。一旦您将脚本评估为绑定,您随后可以有效地调用它定义的函数((JSObject)bindings.get(fnName).call(this,args ...)

If you must share state across threads, then at least try to make it not mutable. If your objects are immutable, you might as well evaluate the script into single Bindings instance and then just use that across threads (invoking hopefully side-effect free functions). If it is mutable, you'll have to synchronize; either the whole bindings, or you can also use the var syncFn = Java.synchronized(fn, lockObj) Nashorn-specific JS API to obtain versions of JS functions that synchronize on a specific object.

如果必须跨线程共享状态,那么至少尝试使其不可变。如果你的对象是不可变的,你也可以将脚本评估为单个Bindings实例,然后只在线程中使用它(调用有希望的副作用*函数)。如果它是可变的,你将不得不同步;要么是整个绑定,要么也可以使用var syncFn = Java.synchronized(fn,lockObj)Nashorn特定的JS API来获取在特定对象上同步的JS函数版本。

This presupposes that you share single bindings across threads. If you want to have multiple bindings share a subset of objects (e.g. by putting the same object into multiple bindings), again, you'll have to somehow deal with ensuring that access to shared objects is thread safe yourself.

这预示着您在线程之间共享单个绑定。如果你想让多个绑定共享一个对象的子集(例如,通过将同一个对象放入多个绑定中),那么你将不得不以某种方式处理确保自己对共享对象的访问是线程安全的。

As for THREADING parameter returning null: yeah, initially we planned on not making the engine threadsafe (saying that the language itself isn't threadsafe) so we chose the null value. We might need to re-evaluate that now, as in the meantime we did make it so that engine instances are threadsafe, just the global scope (bindings) isn't (and never will be, because of JavaScript language semantics.)

至于THREADING参数返回null:是的,最初我们计划不使引擎线程安全(说语言本身不是线程安全的),所以我们选择了null值。我们现在可能需要重新评估,因为在此期间我们确实使得引擎实例是线程安全的,只是全局范围(绑定)不是(并且永远不会,因为JavaScript语言语义。)

#2


5  

ScriptEngine for Nashorn is not thread-safe. This can be verified by calling the ScriptEngineFactory.getParameter("THREADING") of the ScriptEngineFactory for Nashorn.

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

The value returned is null which according to java doc means not thread safe.

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

Note: This part of the answer was first given here. But I rechecked the results and doc myself.

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

This gives us the answer for CompiledScript as well. According to java doc a CompiledScript is associated to one ScriptEngine.

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

So in Nashorn ScriptEngine and CompiledScript should not be used by two threads at the same time.

所以在Nashorn中,ScriptEngine和CompiledScript不应该同时被两个线程使用。

#3


0  

Code sample for @attilla's response

@ attilla响应的代码示例

  1. my js code is something like this:

    我的js代码是这样的:

    
    var renderServer = function renderServer(server_data) {
       //your js logic...
       return html_string.
    }
    
  2. java code:

    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();
       }
    }
    

be careful when using renderServer method in multi-threaded environment, since bindings are not thread safe. One solution is to use multiple instances of renderServer with re-usable object pools. I am using org.apache.commons.pool2.impl.SoftReferenceObjectPool, which seems to be performing well for my use case.

在多线程环境中使用renderServer方法时要小心,因为绑定不是线程安全的。一种解决方案是使用具有可重用对象池的多个renderServer实例。我正在使用org.apache.commons.pool2.impl.SoftReferenceObjectPool,这似乎对我的用例表现良好。

#4


0  

The accepted answer will mislead many people.

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

In short:

简而言之:

  • NashornScriptEngine is NOT thread-safe
  • NashornScriptEngine不是线程安全的
  • If you do NOT use global bindings, the state-less part can be thread-safe
  • 如果不使用全局绑定,则无状态部分可以是线程安全的

#1


50  

You can share a ScriptEngine and CompiledScript objects across threads. They are threadsafe. Actually, you should share them, as a single engine instance is a holder for a class cache and for JavaScript objects' hidden classes, so by having only one you cut down on repeated compilation.

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

What you can't share is Bindings objects. The bindings object basically corresponds to the JavaScript runtime environment's Global object. The engine starts with a default bindings instance, but if you use it in multithreaded environment, you need to use engine.createBindings() to obtain a separate Bindings object for every thread -- its own global, and evaluate the compiled scripts into it. That way you'll set up isolated global scopes with the same code. (Of course, you can also pool them, or synchronize on 'em, just make sure there's never more than one thread working in one bindings instance). Once you evaluated the script into the bindings, you can subsequently efficiently invoke functions it defined with ((JSObject)bindings.get(fnName).call(this, args...)

你不能分享的是Bindings对象。绑定对象基本上对应于JavaScript运行时环境的Global对象。引擎以默认绑定实例启动,但如果在多线程环境中使用它,则需要使用engine.createBindings()为每个线程获取单独的Bindings对象 - 它自己的全局,并将编译后的脚本评估到其中。这样,您将使用相同的代码设置隔离的全局范围。 (当然,您也可以对它们进行池化,或者在它们上进行同步,只需确保在一个绑定实例中永远不会有多个线程工作)。一旦您将脚本评估为绑定,您随后可以有效地调用它定义的函数((JSObject)bindings.get(fnName).call(this,args ...)

If you must share state across threads, then at least try to make it not mutable. If your objects are immutable, you might as well evaluate the script into single Bindings instance and then just use that across threads (invoking hopefully side-effect free functions). If it is mutable, you'll have to synchronize; either the whole bindings, or you can also use the var syncFn = Java.synchronized(fn, lockObj) Nashorn-specific JS API to obtain versions of JS functions that synchronize on a specific object.

如果必须跨线程共享状态,那么至少尝试使其不可变。如果你的对象是不可变的,你也可以将脚本评估为单个Bindings实例,然后只在线程中使用它(调用有希望的副作用*函数)。如果它是可变的,你将不得不同步;要么是整个绑定,要么也可以使用var syncFn = Java.synchronized(fn,lockObj)Nashorn特定的JS API来获取在特定对象上同步的JS函数版本。

This presupposes that you share single bindings across threads. If you want to have multiple bindings share a subset of objects (e.g. by putting the same object into multiple bindings), again, you'll have to somehow deal with ensuring that access to shared objects is thread safe yourself.

这预示着您在线程之间共享单个绑定。如果你想让多个绑定共享一个对象的子集(例如,通过将同一个对象放入多个绑定中),那么你将不得不以某种方式处理确保自己对共享对象的访问是线程安全的。

As for THREADING parameter returning null: yeah, initially we planned on not making the engine threadsafe (saying that the language itself isn't threadsafe) so we chose the null value. We might need to re-evaluate that now, as in the meantime we did make it so that engine instances are threadsafe, just the global scope (bindings) isn't (and never will be, because of JavaScript language semantics.)

至于THREADING参数返回null:是的,最初我们计划不使引擎线程安全(说语言本身不是线程安全的),所以我们选择了null值。我们现在可能需要重新评估,因为在此期间我们确实使得引擎实例是线程安全的,只是全局范围(绑定)不是(并且永远不会,因为JavaScript语言语义。)

#2


5  

ScriptEngine for Nashorn is not thread-safe. This can be verified by calling the ScriptEngineFactory.getParameter("THREADING") of the ScriptEngineFactory for Nashorn.

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

The value returned is null which according to java doc means not thread safe.

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

Note: This part of the answer was first given here. But I rechecked the results and doc myself.

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

This gives us the answer for CompiledScript as well. According to java doc a CompiledScript is associated to one ScriptEngine.

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

So in Nashorn ScriptEngine and CompiledScript should not be used by two threads at the same time.

所以在Nashorn中,ScriptEngine和CompiledScript不应该同时被两个线程使用。

#3


0  

Code sample for @attilla's response

@ attilla响应的代码示例

  1. my js code is something like this:

    我的js代码是这样的:

    
    var renderServer = function renderServer(server_data) {
       //your js logic...
       return html_string.
    }
    
  2. java code:

    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();
       }
    }
    

be careful when using renderServer method in multi-threaded environment, since bindings are not thread safe. One solution is to use multiple instances of renderServer with re-usable object pools. I am using org.apache.commons.pool2.impl.SoftReferenceObjectPool, which seems to be performing well for my use case.

在多线程环境中使用renderServer方法时要小心,因为绑定不是线程安全的。一种解决方案是使用具有可重用对象池的多个renderServer实例。我正在使用org.apache.commons.pool2.impl.SoftReferenceObjectPool,这似乎对我的用例表现良好。

#4


0  

The accepted answer will mislead many people.

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

In short:

简而言之:

  • NashornScriptEngine is NOT thread-safe
  • NashornScriptEngine不是线程安全的
  • If you do NOT use global bindings, the state-less part can be thread-safe
  • 如果不使用全局绑定,则无状态部分可以是线程安全的