2010-08-02 247 views
4

我有一個對象需要定期做一些工作,而對象本身是活着的,所以我設計瞭如下內容。基本上是一個Main類,它包含對ScheduledExecutorService實例的引用。在這個例子中,所有的定期工作是打印一個字符串到std。ScheduledExecutorService生命週期?

我期望的代碼的行爲如下所示:

  1. test2的被調用,它創建一個主要目標O1(內它ScheduledExecutorService的)。
  2. test2寄存器在o1上每秒鐘打印一行。
  3. test2返回,o1變成垃圾。
  4. 系統gc啓動到gc o1,它有一個關閉它的本地調度程序的終止方法。

但是,如果我運行這個程序,會發生什麼,它會繼續前進。基本上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(); 
    }  
} 

}

回答

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

現在在主類,我有:

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

及主要的終結:

@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對象,但線程和任務仍在運行。

7

兩個問題:

  1. 默認的線程工廠創建非守護線程。主線程可以結束,但只要存在活動的非守護線程,JVM就不會終止。我相信你將需要編寫一個自定義線程工廠來創建守護進程線程。
  2. 不要依賴被調用的終結器 - 不能保證終結器將在任何特定時間或任何時候被調用。而且,System.gc()調用被定義爲對JVM的建議,而不是命令。在API文檔的措辭是

調用gc方法表明, 的Java虛擬機的努力 向循環使用的對象......

+0

好吧,似乎System.gc()在這種情況下工作,就好像我註釋掉了註冊方法的調用一樣,GC實際上是通過調用System.gc()來正確踢入的。看起來在調度程序中使用的一些靜態上下文包含對匿名Runnable對象的父對象的引用。 我重構了我的代碼以避免此問題。 – 2010-08-03 05:51:16