java - objetos - por que string es inmutable
Herramientas para encontrar errores de datos de Mutable Compartida en Java (5)
Tengo un gran sistema heredado para mantener. La base de código usa subprocesos por todos lados y esos subprocesos comparten una gran cantidad de datos mutables. Lo sé, suena mal. De todos modos, no respondas "reescriba toda la aplicación desde cero" o te votaré por un voto negativo :-) He intentado ejecutar algunas herramientas de análisis estático en la base de código, pero ninguna de ellas parece captar este caso, que ocurre mucho en nuestro código fuente: múltiples hilos son variables de lectura y escritura que no están marcadas como volátiles o sincronizadas. Por lo general, esto ocurre en las variables de tipo "runFlag". Un ejemplo de esto es en Effective Java 2nd edition página 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;
}
}
Este ejemplo nunca termina en Windows / Linux con el parámetro de inicio "-server" dado a Sun JVM. Entonces, ¿hay alguna forma (semi) automática de encontrar estos problemas, o debo confiar totalmente en las revisiones de los códigos?
Chris Grindstaff escribió un artículo FindBugs, Parte 2: Escribir detectores personalizados en el que describe cómo usar el BCEL para agregar sus propias reglas. (BCEL no es la única biblioteca de códigos de bytes, pero es la utilizada por FindBugs).
El siguiente código emite cualquier caso en que un método acceda a un método o campo estático. Podrías ejecutarlo en cualquier tipo que implemente 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();
}
}
Este código emite lo siguiente:
==========================
method:<init>
==========================
method:run
static access:access$0
on type:StopThread
Entonces sería posible escanear el tipo StopThread , encontrar el campo y verificar si es volátil .
Es posible verificar la sincronización, pero puede ser complicado debido a múltiples condiciones de MONITOREXIT. También puede ser difícil subir las pilas de llamadas, pero este no es un problema trivial. Sin embargo, creo que sería relativamente fácil verificar si existe un patrón de error si se ha implementado de manera consistente.
BCEL parece escasamente documentado y realmente peludo hasta que encuentre la clase BCELifier . Si lo ejecuta en una clase, arroja una fuente Java de cómo construiría la clase en BCEL. Ejecutarlo en StopThread le da esto para generar acceso accessor sintético de $ 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();
}
Coverity hace algunas herramientas de análisis estático y dinámico que pueden ayudar.
La última versión de FindBugs intentará verificar que los campos marcados con la anotación @GuardedBy
sean accedidos solo dentro del código de protección apropiado.
Los FindBugs y las herramientas profesionales basadas en él son su mejor esperanza, pero no cuente con que encuentren todos los problemas de concurrencia en su código.
Si las cosas están en tan mal estado, entonces debes complementar las herramientas con el análisis de un experto en concurrencia java humana.
Este es un problema difícil porque probablemente resulte poco realista demostrar la corrección de una base de código existente pero modificada, especialmente frente al uso concurrente.
Coverity Thread Analyzer hace el trabajo, pero eso es bastante caro. IBM Multi-Thread Run-Time Analysis Tool para Java parece ser capaz de detectarlos, pero parece algo más difícil de configurar. Estas son herramientas de análisis dinámico que detectan qué variables reales se accedieron desde diferentes subprocesos sin una sincronización o volatilidad adecuada, por lo que los resultados son más precisos que con el análisis estático, y son capaces de encontrar muchos problemas que el análisis estático no puede detectar.
Si su código está mayormente o al menos parcialmente sincronizado correctamente, la corrección de coincidencias de FindBugs (u otro análisis estático) también podría ayudar, al menos las reglas IS2_INCONSISTENT_SYNC y UG_SYNC_SET_UNSYNC_GET podrían ser buenas para empezar.