Rhino 和并发访问 javax.script.ScriptEngine

Posted

技术标签:

【中文标题】Rhino 和并发访问 javax.script.ScriptEngine【英文标题】:Rhino and concurrent access to javax.script.ScriptEngine 【发布时间】:2012-02-26 15:10:19 【问题描述】:

我通过javax.script API 使用Rhino 1.6r2。我知道 Rhino 引擎声称是 MULTITHREADED: “引擎实现是内部线程安全的,脚本可以同时执行,尽管脚本在一个线程上执行的效果可能对其他线程上的脚本可见。”

我想知道的是,一个脚本执行的效果在什么确切条件下对另一个脚本执行是可见的?在我的代码中,我有时会重复使用ScriptEngine 对象,但对于每次执行,我都会创建一个新的SimpleBindings 并将其传递给eval(String, Bindings)。通过这种安排,内部状态是否可以从一个执行泄漏到另一个执行?如果有,怎么做?

There's a very informative answer here,但它并没有完全告诉我我需要知道什么。

【问题讨论】:

你绑定的是同一个对象吗? 如果我为多次执行绑定同一个对象,那么显然它对所有人都是可见的。但是不,我不会那样做。 那么您到底想弄清楚什么?如果您对不同的绑定对象使用不同的绑定,那么您还关心什么状态? @wort - 我更多地考虑由脚本创建或修改的状态,而不是从 Java 传递到脚本中。如果我在脚本中声明一个全局变量,该变量的范围是什么?如果我修改一些内置的全局变量会发生什么?这些东西是由 javax.script API 指定的,还是由引擎实现者决定的? this 你在找什么吗? 【参考方案1】:

javax.script 包是线程安全的,但如果您的脚本不是,您可能会遇到并发问题。 脚本中的全局变量对所有线程都是可见的。因此,避免在 javascript 函数中使用全局变量

我现在遇到了这个问题。我的javascript如下:

function run()
    regex = 0;
    regex += 1;
    return regex;

我在 ThreadPool(4) 中运行它 10.000 次,并打印结果。

for (int i = 0; i <= 10000; i++)
        executor.submit(new Runnable() 
            @Override
            public void run() 
                try 
                    Double result = (Double) invocable.invokeFunction("run");
                    System.out.println(result);
                 catch (Exception e) 
            
        );
    

这是输出的一部分:

1.0
2.0
1.0
1.0
1.0
1.0
1.0
1.0
1.0
1.0
1.0
2.0
1.0
1.0
0.0

【讨论】:

你说的根本不是真的。如果您为同一脚本引擎实例创建单独的脚本上下文并指定适当的范围 (ENGINE_SCOPE),则不会出现此问题。【参考方案2】:

是的,JSR223 没有指定脚本语言中的变量应如何与给定的Bindings 绑定。因此,实现者完全有可能选择将全局范围变量存储在引擎实例中并在评估脚本时即使给定不同的Bindings 来重用它。

例如,JRuby 的 JSR223 绑定有一种模式以这种方式工作

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;

public class Jsr223Binding 


    private Jsr223Binding() throws ScriptException 
        System.setProperty("org.jruby.embed.localvariable.behavior", "transient");
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("jruby");
        ScriptContext ctx1 = new SimpleScriptContext();
        ScriptContext ctx2 = new SimpleScriptContext();
        engine.eval("$foo = 5\nputs $foo", ctx1);
        engine.eval("puts $foo", ctx2);
    

    public static void main(String[] args) throws ScriptException 
        new Jsr223Binding();
    

【讨论】:

【参考方案3】:

我修改了https://***.com/a/1601465/22769 答案以表明如果您在 eval() 函数中指定上下文,rhino 脚本引擎的执行是完全线程安全的。该示例同时从 5 个不同的线程调用 fibonacci javascript 函数 100 次:

package samplethread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;

public class JSRunner 

    private static final ScriptEngine engine;
    private static final ScriptEngineManager manager;

    private static final String script = "function fibonacci(num)\r\n" + 
            "  var a = 1, b = 0, temp;\r\n" + 
            "\r\n" + 
            "  while (num >= 0)\r\n" + 
            "    temp = a;\r\n" + 
            "    a = a + b;\r\n" + 
            "    b = temp;\r\n" + 
            "    num--;\r\n" + 
            "  \r\n" + 
            "\r\n" + 
            "  return b;\r\n" + 
            " \r\n" + 
            "var out = java.lang.System.out;\n" + 
            "n = 1;" +
            "while( n <= 100 ) " +
            "   out.println(java.lang.Thread.currentThread().getName() +':'+ 'FIB('+ n +') = ' + fibonacci(n));" +
            "   n++;" +
            "   if (java.lang.Thread.interrupted()) " +
            "       out.println('JS: Interrupted::'+Date.now());" +
            "       break;" +
            "   " +
            "\n"; 

    static 
        manager = new ScriptEngineManager();
        engine = manager.getEngineByName("JavaScript");
    

    public static void main(final String... args) throws Exception 
        for(int i = 0;i<5;i++) 
            try 
                final Bindings b = engine.createBindings();
                final SimpleScriptContext sc = new SimpleScriptContext();
                sc.setBindings(b, ScriptContext.ENGINE_SCOPE);
                execWithFuture(engine, script,sc);
            
            catch(Exception e) 
                e.printStackTrace();
            
        
    

    private static void execWithFuture(final ScriptEngine engine, final String script,final ScriptContext sc) throws Exception 
        System.out.println("Java: Submitting script eval to thread pool...");
        ExecutorService single = Executors.newSingleThreadExecutor();
        Callable<String>  c = new Callable<String>() 

            public String call() throws Exception 
                String result = null;
                try 
                    engine.eval(script,sc);         
                 catch (ScriptException e) 
                    result = e.getMessage();
                 
                return result;
            
        ;

        single.submit(c);
        single.shutdown();
        System.out.println("Java: ...submitted.");
       

【讨论】:

以上是关于Rhino 和并发访问 javax.script.ScriptEngine的主要内容,如果未能解决你的问题,请参考以下文章

Java 变量参数和 rhino

如何解决 javax.script.ScriptException?

在 android 中引用 javax.script.ScriptEngine 或评估 javascript 表达式

Rhino:Java 数字的行为不像 Javascript 数字

使用 require.js 和 Java/Rhino 解析模块

使用 Rhino(Mozilla 的 rhino)的优点