2011-12-27 3 views
11

Интервьюер спросил меня, чтоКак реализовать счетчик объекта в Java

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

Я replyed, что с следующим кодом

public class Foo { 
    private static int count = 0; 

    public Foo() { 
    incrementCount(); 
    } 

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

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

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

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

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

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

+1

Я не вижу причин, почему это было приостановлено ... это интересный и конкретный вопрос. +1 – Joel

+4

FYI, используя AtomicInteger вместо int, будет быстрее, потому что вы можете избежать синхронизированного блока. – LazyCubicleMonkey

ответ

6

Вы можете обернуть нити RunnableRunnable внутри другого, что бы уменьшить счетчик:

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.

+2

Также в интервью я мог бы дать пару бонусных баллов за использование 'AtomicInteger' против синхронизации –

+3

+1 для спецификатора доступа класса Foo или метода incrementCount(). –

3

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

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

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

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

1

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

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

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

+2

Вместо этого я мог бы использовать «WeakReference», так как SoftReference может помешать тому, чтобы объект был GC'ed даже после того, как он вышел из сферы действия (для практических целей) - объекты с мягким достижением могут не быть восстановлены до тех пор, пока не будет реальной потребности в памяти , в то время как слабо достижимый объект умрет как можно скорее. –

Смежные вопросы