ScheduledExecutorService生命周期?

时间:2010-08-02 01:00:34

标签: java multithreading daemon executor

我有一个对象需要定期做一些工作,而对象本身还活着,所以我设计了类似下面的东西。基本上是一个Main类,它包含对ScheduledExecutorService实例的引用。在此示例中,所有定期工作都是将字符串打印到std。

我希望代码的行为如下:

  1. test2被调用,它创建一个Main对象o1(在其中包含ScheduledExecutorService)。
  2. test2注册在o1上每秒打印一行。
  3. test2返回,o1变成垃圾。
  4. 系统gc启动到gc o1,它有一个finalize方法来关闭它的本地调度程序。
  5. 然而,如果我运行这个程序,会发生什么,它会永远发生。基本上gc从不调用o1的终结器,因此,调度程序永远不会关闭,因此,即使主线程结束,程序仍然不会退出。

    现在,如果我在test2()中注释掉o1.register,那么程序的行为就像它应该的那样,例如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 个答案:

答案 0 :(得分:7)

两个问题:

  1. 默认线程工厂创建非守护程序线程。主线程可以结束,但只要存在活动的非守护程序线程,JVM就不会终止。我相信你需要编写一个创建守护程序线程的自定义线程工厂。
  2. 不依赖于被调用的终结者 - 无法保证在任何特定时间或任何时候都会调用终结器。此外,System.gc()调用被定义为JVM的建议,而不是命令。 API文档中的措辞是
  3.   

    调用gc方法表明了这一点   Java虚拟机花费精力   回收未使用的物品...

答案 1 :(得分:3)

在玩WeakReference和ScheduledExecutorService之后,我想我现在对这个问题有了更好的理解。我的代码中的核心问题是以下方法register()。它使用匿名对象Runnable。像这样的匿名对象的问题是它会创建一个强引用回到父作用域。请记住,如果在父作用域“final”中创建字段,则可以在Runnable的run()方法中引用它们。如果我不从我的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());
        }
    }
}

现在在Main课程中,我有:

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

使用此设置,代码按预期运行。例如。当不再引用Main对象时,GC将启动并调用终结器。我做的另一个实验是没有_future.cancel(true);在finalize()中,当Main对象被GC编辑时,Runnable.run()中的弱引用不能再取消引用Main对象,但线程和任务仍在运行。