Как реализовать счетчик объектов в Java

интервьюер спросил меня об этом

как вы можете реализовать класс Foo, где вы сможете рассчитывать экземпляры этого класса. Есть больше потоков, которые создают экземпляр класса Foo.

я ответил, что со следующим кодом

public class Foo {
    private static int count = 0;

    public Foo() {
    incrementCount();
    }

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

Она снова спросила меня, что

если поток заканчивается, счетчик должен быть уменьшен, как вы можете это сделать это?

Я не ответил на этот вопрос.

Я знаю, о finalize() способ, но это зависит от Garbage collector, когда этот метод будет вызван, даже если мы переопределяем finalize().

у меня еще нет решения, можете ли вы объяснить это, пожалуйста?

3 ответов


вы могли бы обернуть нити Runnable внутри Runnable это уменьшит счетчик:

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

проблема с этим, если Runnable r создает несколько экземпляров Foo. Вам нужно будет как-то отслеживать, сколько экземпляров создал поток. Вы можете сделать это, используя ThreadLocal<Integer>, а затем вызвать decrementCounter(), в finally блок, соответствующее количество раз. Полный рабочий пример см. ниже.

если вы можете избежать его, вы не должны полагаться на поведение GC, как это довольно impredictable! Если вы настаиваете на работе с сборщиком мусора, вы должны использовать ссылочные очереди - и чтобы использовать его правильно, вы должны изучить концепцию объект достижимости: http://docs.oracle.com/javase/7/docs/api/index.html?java/lang/ref/package-summary.html

в качестве заключительного замечания, если бы я брал у вас интервью, я бы попытался заставить вас понять, что код, который вы предлагаете, не полностью удовлетворяет требования: вы должны были бы сделать класс final, или метод incrementCount() final или private. Или, проще, вы можете увеличить количество в блоке инициализатора экземпляра: нет необходимости думать о методах, переопределяемых в подклассах или недавно добавленных конструкторах, не увеличивающих количество.


полный пример:

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

таким образом, Вы можете передать этот ThreadFactory в пул потоков, например, или вы можете использовать его самостоятельно, когда хотите построить нить:(new FooCountingThreadFactory()).newThread(job);

в любом случае, все еще есть проблема с этим подходом: если поток создает экземпляры Foo и сохраняет их в глобальной области (читать:static fields), то эти экземпляры будут все еще живы после того, как поток умер, и счетчик все равно будет уменьшен до 0.


делая то же самое в обратном направлении.

поскольку Sun (Oracle) устарел небезопасные методы уничтожения потоков ( почему-нить. ... нерекомендуемый?) ваш поток "выходит", возвращаясь из своего run() метод.

создать decrementCount() метод Foo класс и обязательно позвоните ему перед возвращением из run() в вашей ветке.

поскольку в Java нет деструкторов и, как вы указываете,finalize() полагается на GC ... не существует автоматического способа сделать это. Единственный другой вариант, который я мог бы придумать, - это создать / использовать пул, но это немного отличается.


Я полагаю, вы также можете создать новый SoftReference к вновь созданному экземпляру в конструкторе и соберите их в статическом списке.

Если вы хотите количество экземпляров, вы можете подсчитать ссылки, которые все еще живы.

таким образом, счетчик ссылок уменьшается, когда сборщик мусора выполнит свою работу.