14

我有一个大型遗留系统需要维护。代码库到处都使用线程,这些线程共享大量可变数据。我知道,听起来很糟糕。无论如何,不​​要回答“从头开始重写整个应用程序”,否则我会投票给你 :-) 我曾尝试在代码库上运行一些静态分析工具,但似乎没有一个能捕捉到这种经常发生的情况在我们的源代码中:多个线程正在读取和写入根本没有标记为 volatile 或同步的变量。通常这发生在“runFlag”类型的变量上。在 Effective Java 2nd edition page 260 上就是一个例子:

public class StopThread
{
    private static boolean stopRequested;
    public static void main(String[] args) throws InterruptedException
    {
        Thread backgroundThread = new Thread(new Runnable()
        {
            public void run()
            {
                int i = 0;
                while (!stopRequested)
                {
                    i++;
                }
            }
        });
        backgroundThread.start();
        Thread.sleep(1000);
        stopRequested = true;
    }
}

这个例子永远不会在 Windows/Linux 上完成,并为 Sun JVM 提供了“-server”启动参数。那么,是否有任何(半)自动的方法来发现这些问题,还是我必须完全依赖代码审查?

4

5 回答 5

6

Chris Grindstaff 写了一篇文章FindBugs,第 2 部分:编写自定义检测器,他在其中描述了如何使用BCEL添加您自己的规则。(BCEL 不是唯一的字节码库——但它是 FindBugs 使用的。)

下面的代码发出方法访问静态方法或字段的任何情况。您可以在任何实现Runnable的类型上运行它。

public class StaticInvocationFinder extends EmptyVisitor {

    @Override
    public void visitMethod(Method obj) {
        System.out.println("==========================");
        System.out.println("method:" + obj.getName());

        Code code = obj.getCode();
        InstructionList instructions = new InstructionList(code.getCode());
        for (Instruction instruction : instructions.getInstructions()) {
            // static field or method
            if (Constants.INVOKESTATIC == instruction.getOpcode()) {
                if (instruction instanceof InvokeInstruction) {
                    InvokeInstruction invokeInstruction = (InvokeInstruction) instruction;
                    ConstantPoolGen cpg = new ConstantPoolGen(obj
                            .getConstantPool());
                    System.out.println("static access:"
                            + invokeInstruction.getMethodName(cpg));
                    System.out.println("      on type:"
                            + invokeInstruction.getReferenceType(cpg));
                }
            }
        }
        instructions.dispose();
    }

    public static void main(String[] args) throws Exception {
        JavaClass javaClass = Repository.lookupClass("StopThread$1");

        StaticInvocationFinder visitor = new StaticInvocationFinder();
        DescendingVisitor classWalker = new DescendingVisitor(javaClass,
                visitor);
        classWalker.visit();
    }

}

此代码发出以下内容:

==========================
method:<init>
==========================
method:run
static access:access$0
      on type:StopThread

然后可以扫描StopThread类型,找到该字段并检查它是否是volatile

检查同步是可能的,但由于多个 MONITOREXIT 条件可能会变得棘手。向上调用堆栈也可能很困难,但这不是一个微不足道的问题。但是,我认为如果始终如一地实施,检查错误模式会相对容易。

在您找到BCELifier类之前,BCEL 看起来很少有文档记录并且非常麻烦。如果你在一个类上运行它,它会吐出你将如何在 BCEL 中构建类的 Java 源代码。在StopThread上运行它可以生成access$0合成访问器:

  private void createMethod_2() {
    InstructionList il = new InstructionList();
    MethodGen method = new MethodGen(ACC_STATIC | ACC_SYNTHETIC, Type.BOOLEAN, Type.NO_ARGS, new String[] {  }, "access$0", "StopThread", il, _cp);

    InstructionHandle ih_0 = il.append(_factory.createFieldAccess("StopThread", "stopRequested", Type.BOOLEAN, Constants.GETSTATIC));
    il.append(_factory.createReturn(Type.INT));
    method.setMaxStack();
    method.setMaxLocals();
    _cg.addMethod(method.getMethod());
    il.dispose();
  }
于 2008-10-09T19:22:53.937 回答
2

最新版本的 FindBugs 将尝试检查标有@GuardedBy注释的字段是否只能在适当的保护代码中访问。

于 2008-10-09T13:15:41.433 回答
2

Coverity 线程分析器可以完成这项工作,但这非常昂贵。IBM Multi-Thread Run-time Analysis Tool for Java 似乎能够检测到这些,但设置起来似乎有些困难。这些是动态分析工具,可以检测哪些实际变量是在没有适当同步或波动的情况下从不同线程访问的,因此结果比静态分析更准确,并且能够发现静态分析无法检测到的许多问题。

如果您的代码大部分或至少部分正确同步,修复 FindBugs(或其他静态分析)并发检查也可能会有所帮助,至少规则 IS2_INCONSISTENT_SYNC 和 UG_SYNC_SET_UNSYNC_GET 可能是好的开始。

于 2008-11-18T20:22:54.283 回答
1

Coverity 制作了一些可能有所帮助的静态和动态分析工具。

http://www.coverity.com/

于 2008-10-09T16:51:43.447 回答
1

FindBugs 和基于它的专业工具是您最大的希望,但不要指望它们会发现您代码中的所有并发问题。

如果情况如此糟糕,那么您应该通过人类 Java 并发专家的分析来补充工具。

这是一个难题,因为最终证明现有但经过修改的代码库的正确性可能是不现实的——尤其是在并发使用的情况下。

于 2008-10-09T12:35:08.083 回答