【问题标题】:Lock contention in Groovy Shell interpreter under high load高负载下 Groovy Shell 解释器中的锁争用
【发布时间】:2018-01-16 14:53:00
【问题描述】:

我们正在我们的应用程序中评估 GroovyShell 解释器 (v2.4) 以动态执行标准 Java 语法。

早些时候,我们使用 Java BeanShell 解释器,但它在高负载下有一个 issue,这促使我们寻找替代方案,例如 Groovy。

Java 代码示例

static String script = "int y = x * x; System.out.println(\"** value of y ** :: \" + y ); ";

GroovyShell gs = new GroovyShell();
Script evalScript = gs.parse("void evalMethod() {" + script + "}");
// bind variables
Binding binding = new Binding();
binding.setVariable("x", 5);
evalScript.setBinding(binding);
// invoke eval method
evalScript.invokeMethod("evalMethod", null);

当多个线程同时执行上述代码时,我们会看到线程锁争用。我们有多个线程处于阻塞状态,这会降低应用程序的性能。

阻塞的线程调用堆栈

java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
- waiting to lock <0x00000007bc425e40> (a java.lang.Object)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:411)
- locked <0x00000007bd369ba8> (a groovy.lang.GroovyClassLoader)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:677)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:545)
at org.codehaus.groovy.control.ClassNodeResolver.tryAsLoaderClassOrScript(ClassNodeResolver.java:185)
at org.codehaus.groovy.control.ClassNodeResolver.findClassNode(ClassNodeResolver.java:170)
at org.codehaus.groovy.control.ClassNodeResolver.resolveName(ClassNodeResolver.java:126)
at org.codehaus.groovy.control.ResolveVisitor.resolveToOuter(ResolveVisitor.java:676)
at org.codehaus.groovy.control.ResolveVisitor.resolve(ResolveVisitor.java:313)
at org.codehaus.groovy.control.ResolveVisitor.transformPropertyExpression(ResolveVisitor.java:845)
at org.codehaus.groovy.control.ResolveVisitor.transform(ResolveVisitor.java:696)
at org.codehaus.groovy.control.ResolveVisitor.transformMethodCallExpression(ResolveVisitor.java:1081)
at org.codehaus.groovy.control.ResolveVisitor.transform(ResolveVisitor.java:702)
at org.codehaus.groovy.ast.ClassCodeExpressionTransformer.visitExpressionStatement(ClassCodeExpressionTransformer.java:142)
at org.codehaus.groovy.ast.stmt.ExpressionStatement.visit(ExpressionStatement.java:42)
at org.codehaus.groovy.ast.CodeVisitorSupport.visitBlockStatement(CodeVisitorSupport.java:37)
at org.codehaus.groovy.ast.ClassCodeVisitorSupport.visitBlockStatement(ClassCodeVisitorSupport.java:166)
at org.codehaus.groovy.control.ResolveVisitor.visitBlockStatement(ResolveVisitor.java:1336)
at org.codehaus.groovy.ast.stmt.BlockStatement.visit(BlockStatement.java:71)
at org.codehaus.groovy.ast.ClassCodeVisitorSupport.visitClassCodeContainer(ClassCodeVisitorSupport.java:104)
at org.codehaus.groovy.ast.ClassCodeVisitorSupport.visitConstructorOrMethod(ClassCodeVisitorSupport.java:115)
at org.codehaus.groovy.ast.ClassCodeExpressionTransformer.visitConstructorOrMethod(ClassCodeExpressionTransformer.java:53)
at org.codehaus.groovy.control.ResolveVisitor.visitConstructorOrMethod(ResolveVisitor.java:201)
at org.codehaus.groovy.ast.ClassCodeVisitorSupport.visitMethod(ClassCodeVisitorSupport.java:126)
at org.codehaus.groovy.ast.ClassNode.visitContents(ClassNode.java:1081)
at org.codehaus.groovy.ast.ClassCodeVisitorSupport.visitClass(ClassCodeVisitorSupport.java:53)
at org.codehaus.groovy.control.ResolveVisitor.visitClass(ResolveVisitor.java:1279)
at org.codehaus.groovy.control.ResolveVisitor.startResolving(ResolveVisitor.java:176)
at org.codehaus.groovy.control.CompilationUnit$12.call(CompilationUnit.java:663)
at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:943)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:605)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:554)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
- locked <0x00000007bd372240> (a java.util.HashMap)
at groovy.lang.GroovyShell.parseClass(GroovyShell.java:688)
at groovy.lang.GroovyShell.parse(GroovyShell.java:700)
at groovy.lang.GroovyShell.parse(GroovyShell.java:736)
at groovy.lang.GroovyShell.parse(GroovyShell.java:727)

我的问题是:

  1. 有人遇到过这样的问题吗?如果是,您能否建议任何可能的解决方案?我用谷歌搜索了这个问题,很少有博客建议缓存 groovy Script 对象。 Script 对象是线程安全的吗?此外,我需要将变量绑定到 Script 对象(通过 groovy Binding 对象,不同线程中的每次执行都不同),所以我不认为缓存 groovy Script 对象是一个可行的选择。李>
  2. 在将 Groovy Shell 解释器与 Java 结合使用时,是否需要遵循任何最佳实践?
  3. 我们可以考虑其他任何库吗?我的要求是动态执行标准 Java 语法。

【问题讨论】:

    标签: java multithreading groovy groovyshell


    【解决方案1】:

    groovy parse&compile 是一个相当繁重的部分

    如果您的脚本是静态的 - 那么您只需解析一次

    如果脚本总是不同但重复 - 您可以将已编译的 groovy 脚本缓存到

    ConcurrentHashMap<String, Class<groovy.lang.Script>>
    

    之后

    Script evalScript = gs.parse(...);
    

    只需将evalScript.getClass() 放入已编译脚本的缓存中即可。

    gs.parse(...) 之前检查你是否已经编译了脚本,如果存在的话,只需获取缓存类的新实例。

    检查以下示例和执行速度

    String script = "int y = x * x; System.out.println(\"** value of y ** :: \" + y ); ";
    
    def ts = System.currentTimeMillis()
    for(int i=0;i<100;i++){
        GroovyShell gs = new GroovyShell();
        Script evalScript = gs.parse("void evalMethod() {" + script + "}");
        // bind variables
        Binding binding = new Binding();
        binding.setVariable("x", i);
        evalScript.setBinding(binding);
        // invoke eval method
        evalScript.invokeMethod("evalMethod", null);
    }
    println ">> ${System.currentTimeMillis() - ts} millis"
    

    1165 毫秒

    String script = "int y = x * x; System.out.println(\"** value of y ** :: \" + y ); ";
    GroovyShell gs = new GroovyShell();
    Class<Script> scriptClass = gs.parse("void evalMethod() {" + script + "}").getClass();
    
    def ts = System.currentTimeMillis()
    for(int i=0;i<100;i++){
        Script evalScript = scriptClass.newInstance();
        // bind variables
        Binding binding = new Binding();
        binding.setVariable("x", i);
        evalScript.setBinding(binding);
        // invoke eval method
        evalScript.invokeMethod("evalMethod", null);
    }
    println ">> ${System.currentTimeMillis() - ts} millis"
    

    6 毫秒

    【讨论】:

    • 谢谢!缓存已编译的脚本类实例 (evalScript.getClass()) 是否会导致任何内存泄漏或延迟清理生成的类和元类?
    • 如果您有无限数量的脚本,那么您必须考虑清理缓存。而不是ConcurrentHashMap,你可以使用一些简单的东西:Collections.synchronizedMap(new WeakHashMap&lt;String, Class&gt;())或一些内存缓存,如ehcache或任何其他......
    • 请注意,这不是线程安全的。不同步和并行调用 setBinding 和 invokeMethod 可能会导致意外结果。因此,使用 ConcurrentHashMap 具有误导性。 -> stackoverflow.com/questions/63465935/groovyshell-thread-safety
    • 如果您将不同的参数绑定到同一个实例,这是正确的。但如果您使用scriptClass.newInstance() - 没有问题。所以,concurrentHashMap 是相关的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    • 2016-11-15
    相关资源
    最近更新 更多