我有一个庞大的遗留系统需要维护。代码库使用遍布各处的线程,这些线程共享大量可变数据。我知道,听起来很糟糕。无论如何,不回答“从头开始重写整个应用程序”或者我会投票给你:-)我试图在代码库上运行一些静态分析工具,但这些似乎都没有抓住这种情况发生了很多在我们的源代码中:多个线程正在读取和写入未标记为volatile或同步的变量。通常这发生在“runFlag”类型的变量上。这方面的一个例子是在Effective Java 2nd edition第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”启动参数。那么,有没有(半)自动方式来找到这些问题,还是我必须完全依赖代码审查?
答案 0 :(得分:6)
Chris Grindstaff撰写了一篇文章FindBugs, Part 2: Writing custom detectors,其中介绍了如何使用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();
}
答案 1 :(得分:2)
最新版本的FindBugs将尝试检查标有@GuardedBy
注释的字段是否仅在相应的保护代码中访问。
答案 2 :(得分:2)
Coverity线程分析器完成这项工作,但这非常昂贵。用于Java的IBM多线程运行时分析工具似乎能够检测到这些,但设置起来有点困难。这些是动态分析工具,可以检测从不同线程访问哪些实际变量而没有适当的同步或波动,因此结果比静态分析更准确,并且能够找到静态分析无法检测到的许多问题。
如果您的代码大部分或至少部分正确同步,修复FindBugs(或其他静态分析)并发检查也可能有所帮助,至少规则IS2_INCONSISTENT_SYNC和UG_SYNC_SET_UNSYNC_GET可能是好的开始。
答案 3 :(得分:1)
基于它的FindBugs和专业工具是你最好的希望,但不要指望他们找到代码中的所有并发问题。
如果事情的形状很糟糕,那么你应该通过人类java并发专家的分析来补充工具。
这是一个难题,因为最终确定现有但经过修改的代码库的正确性可能不切实际 - 尤其是在并发使用的情况下。
答案 4 :(得分:1)
Coverity提供了一些可能有用的静态和动态分析工具。