2011-12-27 30 views
11

Un entrevistador me preguntó queCómo implementar contador de objetos en Java

¿Cómo se puede implementar una clase Foo, donde usted será capaz de contar instancias de esa clase. Hay más hilos que están creando la instancia de esa clase Foo.

I replyed que con el siguiente código

public class Foo { 
    private static int count = 0; 

    public Foo() { 
    incrementCount(); 
    } 

    public void incrementCount() { 
     synchronize (Foo.class) { 
      count++; 
     } 
    } 
} 

Nuevamente me pidió que

Si termina un hilo, contador debe decremento, ¿cómo se puede hacer eso?

No he respondido esta pregunta.

Conozco el método finalize(), pero depende de Garbage collector que cuando se llame a este método, incluso si anulamos finalize().

Todavía no tengo una solución, ¿pueden explicarla por favor?

+1

No veo ninguna razón por la cual esto fue desestimado ... es una pregunta interesante y específica. +1 – Joel

+4

FYI, usar AtomicInteger en lugar de int será realmente más rápido porque puede evitar el bloqueo sincronizado. – LazyCubicleMonkey

Respuesta

6

uno puede cerrar de Runnable el hilo dentro de otro Runnable que disminuir el contador:

Thread createThread(final Runnable r) { 
    return new Thread(new Runnable() { 
    @Override public void run() { 
     try { 
     r.run(); 
     } finally { 
     Foo.decrementCounter(); 
     } 
    } 
    }); 
} 

El problema con esto es que si el Runnable r crea varias instancias de Foo. Tendría que rastrear de alguna manera cuántas instancias creó el hilo. Puede hacerlo usando un ThreadLocal<Integer>, y luego llamar al decrementCounter(), en el bloque finally, la cantidad apropiada de veces. Vea a continuación un ejemplo completo y funcional.

Si puede evitarlo, no debe confiar en el comportamiento del GC ya que es bastante impredecible. Si insiste en tratar con el recolector de basura, entonces debería usar colas de referencia - y utilizarlo correctamente, se debe estudiar el concepto de accesibilidad objeto : http://docs.oracle.com/javase/7/docs/api/index.html?java/lang/ref/package-summary.html

Como nota final, si te entrevistaban , Trataría de hacerte comprender que el código que propones no cumple perfectamente con los requisitos: tendrías que hacer la clase final, o el método incrementCount()final o private. O, más fácil, podría incrementar el recuento en un bloque de inicializador de instancia: no hay necesidad de pensar en métodos que se anulan en subclases o constructores recién agregados que no incrementan el recuento.


Un ejemplo completo:

public class Foo { 
    private static final AtomicInteger liveInstances = new AtomicInteger(0); 
    private static final ThreadLocal<Integer> threadLocalLiveInstances = new ThreadLocal<Integer>() { 
    @Override protected Integer initialValue() { return 0; } 
    } 

    // instance initializer (so you won't have problems with multiple constructors or virtual methods called from them): 
    { 
    liveInstances.incrementAndGet(); 
    threadLocalLiveInstances.set(threadLocalLiveInstances.get() + 1); 
    } 

    public static int getTotalLiveInstances() { 
    return liveInstances.get(); 
    } 

    public static int getThreadLocalLiveInstances() { 
    return threadLocalLiveInstances.get(); 
    } 

    public static void decrementInstanceCount() { 
    threadLocalLiveInstances.set(threadLocalLiveInstances.get() - 1); 
    liveInstaces.decrementAndGet(); 
    } 

    // ... rest of the code of the class ... 
} 

class FooCountingThreadFactory implements ThreadFactory { 
    public Thread newThread(final Runnable r) { 
    return new Thread(new Runnable() { 
     @Override public void run() { 
     try { 
      r.run(); 
     } finally { 
      while (Foo.getThreadLocalLiveInstances() > 0) { 
      Foo.decrementInstanceCount(); 
      } 
     } 
     } 
    }); 
    } 
} 

De esta manera, se puede alimentar esta ThreadFactory a un grupo de subprocesos, por ejemplo, o se puede utilizar por sí mismo cuando se quiere construir un hilo: (new FooCountingThreadFactory()).newThread(job);

De todos modos, todavía hay un problema con este enfoque: si un hilo crea instancias de Foo y las almacena en ámbito global (lea: static campos), estas instancias seguirán activas después de que el hilo haya muerto, y el el contador se reducirá igualmente a 0.

+2

También en una entrevista podría dar un par de puntos de bonificación por usar 'AtomicInteger' frente a sincronizar –

+3

+1 para el especificador de acceso de la clase Foo o el método incrementCount(). –

3

Haciendo exactamente lo mismo al revés.

Dado que Sun (Oracle) desaprobó los métodos inseguros de matar a los hilos (Why are Thread. ... deprecated?) su hilo "sale" volviendo de su método run().

Simplemente cree un método decrementCount() en su clase Foo y asegúrese de llamarlo antes de regresar de run() en su hilo.

Dado que no hay destructores en Java y, como usted señala, finalize() depende del GC ... no hay realmente una manera automática de hacerlo. La única otra opción que podría pensar sería crear/usar un grupo, pero eso es un poco diferente.

1

Supongo que también puede crear un nuevo SoftReference en la instancia recién creada en el constructor y recopilarlos en una lista estática.

Si desea contar la instancia, puede contar las referencias que aún están activas.

De esta manera, el recuento de referencias se reduce cuando el recolector de basura ha hecho su trabajo.

+2

Podría utilizar una 'WeakReference' en su lugar, ya que una SoftReference podría evitar que el objeto sea sometido a GC incluso después de que se haya salido del alcance (para fines prácticos): los objetos de alcance débil podrían no recuperarse hasta que haya una necesidad real de memoria , mientras que el objeto débilmente alcanzable morirá lo más pronto posible. –