我有一个场景,我正在处理用户提供的包含JavaScript部分的模板。我正在使用Java的ScriptEngine
来评估那些JavaScript。我有各种帮助函数,我只想评估一次(想想这些帮助函数加载像Underscore.js
这样的东西)。
加载完成后,我遍历数据行并处理每行的模板 - 这些模板可以使用任何以前加载的辅助函数。我试图并行处理行,因为Java的ScriptEngine.eval()
在这种情况下非常慢。
任何通过简化SSCCE查看此内容的人都会知道我已经获得了我所展示的预期结果,因为我正在以非线程安全方式修改全局对象。在我的例子中,sJsGlobal
是这个全局JavaScript。全局函数需要访问我在obj
中放置的各种特定行的数据。
为了sJsGlobal
成功eval()
它需要知道obj
或它将失败。在我的SSCCE中,每个线程在调用函数之前更新相同的全局obj
。我正在寻找一种方法,让每个全球函数都能看到当地的obj
。这是一个Catch-22情况,我需要为全局脚本定义变量以了解它存在,但我希望它具有本地意义。
import java.util.*;
import java.util.concurrent.*;
import javax.script.*;
public class SSCCE {
public static void main(String[] args) throws Exception {
StringBuilder sJsGlobal = new StringBuilder();
sJsGlobal.append("var obj = {};");
sJsGlobal.append("function get_row() { return \"Row is \" + obj.row }");
ScriptEngine jsGlobal = new ScriptEngineManager().getEngineByName("JavaScript");
jsGlobal.eval(sJsGlobal.toString());
Bindings gB = jsGlobal.getBindings(ScriptContext.ENGINE_SCOPE);
ExecutorService es = Executors.newCachedThreadPool();
ArrayList<Future<String>> af = new ArrayList<Future<String>>();
for (int i = 0; i < 10; i++) {
final int fi = i;
af.add(es.submit(() -> {
StringBuilder sJsLocal = new StringBuilder();
sJsLocal.append("obj.row = " + fi + ";");
sJsLocal.append("var x = get_row();");
ScriptEngine jsLocal = new ScriptEngineManager().getEngineByName("JavaScript");
ScriptContext sc = new SimpleScriptContext();
Bindings lB = jsLocal.createBindings();
lB.putAll(gB);
sc.setBindings(lB, ScriptContext.ENGINE_SCOPE);
jsLocal.setContext(sc);
jsLocal.eval(sJsLocal.toString());
return (String)jsLocal.get("x");
}));
}
for (Future<String> f : af) {
System.out.println("Returned -> " + f.get());
}
es.shutdown();
}
}
由于我使用了这个全局obj
变量,因此导致以下输出被覆盖,因为它不是线程安全的。即使有一个单独的ScriptEngineManager
和ScriptEngine
。我需要一种设置绑定的方法,这样它们就不会干扰其他实例。
Returned -> Row is 4
Returned -> Row is 4
Returned -> Row is 4
Returned -> Row is 4
Returned -> Row is 8
Returned -> Row is 5
Returned -> Row is 6
Returned -> Row is 7
Returned -> Row is 8
Returned -> Row is 9
我可以将行特定数据作为变量传递给每个函数,但是这变得非常混乱,因为我的示例不能确定我需要交换的数据量。这些函数也被用户脚本调用,这使得这非常困难。在评估obj
之前,我真的想找到一种在每个线程中重新定义sJsLocal
的方法。
如果我一个接一个地处理每一行而不进行线程化,这一切都可以正常工作,但我试图加快这一点。
经过大量的试验,错误和努力以及阅读大量类似问题而没有答案,我提出了以下似乎有效的方法,并在此处记录了遇到此问题的其他人。这使用常见的ScriptEngine
和每个线程的单独ScriptContext
来提供隔离。
import java.util.*;
import java.util.concurrent.*;
import javax.script.*;
public class SSCCE {
public static void main(String[] args) throws Exception {
ScriptEngine js = new ScriptEngineManager().getEngineByName("JavaScript");
StringBuilder sJsGlobal = new StringBuilder();
sJsGlobal.append("var obj = {};");
sJsGlobal.append("function get_row() { return \"Row is \" + obj.row; }");
CompiledScript jsLib = ((Compilable)js).compile(sJsGlobal.toString());
ExecutorService es = Executors.newCachedThreadPool();
ArrayList<Future<String>> af = new ArrayList<Future<String>>();
for (int i = 0; i < 10; i++) {
final int fi = i;
af.add(es.submit(() -> {
StringBuilder sJsLocal = new StringBuilder();
sJsLocal.append("obj.row = " + fi + ";");
sJsLocal.append("var x = get_row();");
ScriptContext scLocal = new SimpleScriptContext();
scLocal.setBindings(js.createBindings(), ScriptContext.ENGINE_SCOPE);
jsLib.eval(scLocal);
js.eval(sJsLocal.toString(), scLocal);
return (String)scLocal.getAttribute("x");
}));
}
for (Future<String> f : af) {
System.out.println("Returned -> " + f.get());
}
es.shutdown();
}
}
运行时会产生以下结果:
Returned -> Row is 0
Returned -> Row is 1
Returned -> Row is 2
Returned -> Row is 3
Returned -> Row is 4
Returned -> Row is 5
Returned -> Row is 6
Returned -> Row is 7
Returned -> Row is 8
Returned -> Row is 9