How to implement object counter in Java

2020-07-06 02:27发布

问题:

An interviewer asked me that

How can you implement a class Foo, where you will be able to count instances of that class. There are more threads which are creating instance of that class Foo.

I replyed that with following code

public class Foo {
    private static int count = 0;

    public Foo() {
    incrementCount();
    }

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

She again asked me that

If a thread ends, counter should be decrement, how can you do that?

I didn't answer this question.

I know about finalize() method, but it depends on Garbage collector that when this method will be called, even if we override finalize().

I have no solution yet, can you explain it please?

回答1:

You could wrap the Thread's Runnable inside another Runnable that would decrement the counter:

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

The problem with this is if the Runnable r creates multiple instances of Foo. You'd have to somehow track how many instances the thread created. You could do so using a ThreadLocal<Integer>, and then call decrementCounter(), in the finally block, the appropriate number of times. See below for a complete, working example.

If you can avoid it, you should not rely on the behavior of the GC as it is quite impredictable! If you insist into dealing with the Garbage Collector, then you should use reference queues -- and to use it properly, you should study the concept of object reachability: http://docs.oracle.com/javase/7/docs/api/index.html?java/lang/ref/package-summary.html

As a final note, if I were interviewing you, I'd try to make you realize that the code you propose does not perfectly satisfy the requirements: you'd have to make the class final, or the method incrementCount() final or private. Or, easier, you could increment the count in an instance initializer block: no need to think about methods being overriden in subclasses or newly added constructors not incrementing the count.


A complete example:

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();
          }
        }
      }
    });
  }
}

This way, you can feed this ThreadFactory to a thread pool, for example, or you can use it yourself when you want to build a thread: (new FooCountingThreadFactory()).newThread(job);

Anyways, there's still a problem with this approach: if a thread creates instances of Foo and stores them on global scope (read: static fields), then these instances will still be alive after the thread has died, and the counter will all the same be decremented to 0.



回答2:

By doing the exact same thing in reverse.

Since Sun (Oracle) deprecated the unsafe methods of killing threads ( Why are Thread. ... deprecated? ) your thread "exits" by returning from its run() method.

Simply create a decrementCount() method in your Foo class and make sure to call it before returning from run() in your thread.

Since there's no destructors in Java and, as you point out, finalize() relies on the GC ... there's not really an automatic way to do this. The only other option I could think of would be to create/use a pool but that's slightly different.



回答3:

I suppose you could also create a new SoftReference to the newly created instance in the constructor, and collect those in a static list.

If you want the instance count, you can count the references that are still alive.

This way, the reference count gets reduced when the garbage collector has done its work.