Как реализовать счетчик объектов в Java - PullRequest
11 голосов
/ 27 декабря 2011

Интервьюер спросил меня, что

Как реализовать класс 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 ]

6 голосов
/ 27 декабря 2011

Вы можете обернуть нить 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, так как оно довольно непредсказуемо! Если вы настаиваете на работе с сборщиком мусора, то вам следует использовать справочные очереди - и для правильного использования вы должны изучить концепцию достижимости объекта : 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 поля), то эти экземпляры будут все еще живы после того, как поток умер и счетчик все равно будет уменьшен до 0.

3 голосов
/ 27 декабря 2011

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

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

Просто создайте метод decrementCount() в своем классе Foo и обязательно вызовите его, прежде чем возвращаться из run() в вашем потоке.

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

1 голос
/ 27 декабря 2011

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

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

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

...