ScheduledExecutorService Жизненный цикл? - PullRequest
4 голосов
/ 02 августа 2010

У меня есть объект, который должен периодически выполнять некоторую работу, пока сам объект жив, поэтому я разработал что-то вроде следующего.В основном класс Main, который содержит ссылку на экземпляр ScheduledExecutorService.В этом примере вся периодическая работа заключается в печати строки в std.

Я ожидаю, что код будет вести себя следующим образом:

  1. test2 вызывается, что создает главный объект o1 (внутри него ScheduledExecutorService).
  2. test2 регистр для печати строки каждую секунду на o1.
  3. test2 возвращается, o1 становится мусором.
  4. Система gc запускает gc o1, у которого есть метод finalize для выключения локального планировщика.

Однако, если я запустите эту программу, то произойдет, что она будет работать навсегда.По сути, gc никогда не вызывает финализатор o1, и, как следствие, планировщик никогда не выключается, и в результате, даже когда основной поток завершается, программа по-прежнему не завершает работу.

Теперь, если я закомментирую o1.register в test2 (), программа будет вести себя так, как должна, например, вызывается gc и т. Д. Также в отладчике кажется, что только после вызова ScheduledExecutorService.schedule будет создан фактический поток,

Любое объяснение, что происходит?

public class Main {

public static void main(String[] args) throws Exception {
    test2();

    System.gc();
    System.out.println("Waiting for finalize to be called..");
    Thread.sleep(5000);
}

private static void test2() throws Exception {
    Main o1 = new Main();
    o1.register();
    Thread.sleep(5000);     
}

private final ScheduledExecutorService _scheduler = Executors.newSingleThreadScheduledExecutor();   

private void register() {
    _scheduler.scheduleWithFixedDelay(new Runnable() { 
        @Override public void run() { 
            System.out.println("!doing stuff...");
            }
        }, 1, 1, TimeUnit.SECONDS);
}

@Override
protected void finalize() throws Throwable  {
    try {
        System.out.print("bye");
        _scheduler.shutdown();          
    } finally {
        super.finalize();
    }       
}

} ​​

Ответы [ 2 ]

7 голосов
/ 02 августа 2010

Две проблемы:

  1. Фабрика потоков по умолчанию создает потоки non-daemon .Основной поток может закончиться, но пока есть активные потоки, не являющиеся демонами, JVM не будет завершаться.Я считаю, что вам нужно написать собственную фабрику потоков, которая создает потоки демонов.
  2. Не зависит от вызываемого финализатора - нет гарантии, что финализатор будет вызван в любое конкретное время или когда-либо.Кроме того, вызов System.gc() определяется как предложение для JVM, а не команда.В документе API формулировка:

Вызов метода gc предполагает, что виртуальная машина Java затрачивает усилия на утилизацию неиспользуемых объектов ...

3 голосов
/ 04 августа 2010

После игры с WeakReference и ScheduledExecutorService, я думаю, что теперь я лучше понимаю проблему. Основная проблема в моем коде заключается в следующем методе register (). Он использует анонимный объект Runnable. Проблема с анонимным объектом, подобным этому, заключается в том, что он создает сильную ссылку на родительскую область. Помните, что если вы сделаете поля в родительской области "final", вы можете ссылаться на них из метода run () класса Runnable. Я думал, что я не создаю такой сильный реф, если я не ссылаюсь на что-либо из моего run (). Как показано в этом случае, все, что я делаю в run (), - это вывод некоторой статической строки. Однако, согласно наблюдаемому поведению, такая ссылка, тем не менее, создается.

private void register() {
_scheduler.scheduleWithFixedDelay(new Runnable() { 
    @Override public void run() { 
        System.out.println("!doing stuff...");
        }
    }, 1, 1, TimeUnit.SECONDS);

}

Правильный способ выполнения этого вида программирования - создать класс и передать свой объект самостоятельно. Вы также должны держать только слабую ссылку. Код довольно длинный, я просто опубликую реализацию Runnable, которая содержит слабую ссылку на доменный объект Main.

private static class ResourceRefreshRunner implements Runnable
{
    WeakReference<Main> _weakRef;
    public ResourceRefreshRunner(Main o)
    {
        _weakRef = new WeakReference<Main>(o);
    }       
    @Override
    public void run() { 
        try {
            Main m = _weakRef.get();
            if (m != null) 
                m.shout(); 
            else 
                System.out.println("object not there, but future is running. ");
        } catch (Exception ex) {
            System.out.println(ex.toString());
        }
    }
}

Сейчас в Главном классе у меня есть:

public class Main {
ScheduledExecutorService _poolInstance;
ScheduledFuture<?> _future;
public Main(ScheduledExecutorService p)
{
    _poolInstance = p;
    _future = _poolInstance.scheduleWithFixedDelay(new ResourceRefreshRunner(this), 1, 1, TimeUnit.SECONDS);
}  ...

И финализатор Main:

    @Override
protected void finalize() throws Throwable  {
    try {
        System.out.println("bye");
        _future.cancel(true);
    } finally {
        super.finalize();
    }       
}

С этой настройкой код ведет себя как ожидалось. Например. когда на главный объект больше не ссылаются, включается GC и вызывается финализатор. Еще один эксперимент, который я провел, заключается в том, что без _future.cancel (true); в finalize (), когда объект Main является GC-ed, слабая ссылка в Runnable.run () больше не может разыменовывать объект Main, но поток и задачи все еще работают.

...