2016-10-27 38 views
0

我有一個ExecutorService坐在一個singleton類中,它接收來自許多不同類的任務。在應用程序關閉時,我需要等待池被清空,然後才允許應用程序退出。阻止Java應用程序退出,直到ThreadPool爲空

private static NotificationService instance = null; 

private ExecutorService executorService = Executors.newFixedThreadPool(25); 

public static synchronized NotificationService getInstance() { 
    if (instance == null) { 
     instance = new NotificationService(true); 
    } 
    return instance; 
} 

在使用本NotificationService,它經常發生,我重新啓動應用程序和executorService還沒有處理完所有的通知。

對於測試,我可以手動關閉executorService並等待所有任務完成。

public static boolean canExit() throws InterruptedException { 
    NotificationService service = getInstance(); 
    service.executorService.shutdown(); 
    service.executorService.awaitTermination(30, TimeUnit.SECONDS); 
    return service.executorService.isTerminated(); 
} 

覆蓋finalize方法並等待直到池爲空是否可靠和安全?從我讀過的內容來看,並不總是調用finalize,尤其是在使用單例類時。

@Override 
protected void finalize() throws Throwable { 
    while (!canExit()){ 
     Thread.sleep(100); 
    } 
    super.finalize(); 
} 

此代碼包含在將包含在另一個應用程序庫,所以沒有主要的方法,我可以等到池是空的,除非我用它這樣做是不強迫人大。

什麼是正確的方式來拖延應用程序(一段合理的時間)從終止,直到池是空的?

+0

你的實現有什麼問題? canExit()會阻止,直到執行器完成或30秒。把它放在一個循環中,直到它返回true,你應該沒問題。 – Fildor

+0

其他問題:實際處理所有通知至關重要嗎?或者你能否比你能夠更快地「取消」它們? - 當然,給定一個關機場景。 – Fildor

+0

@Fidor,當我控制一切時,canExit可以工作,如前所述,這將用於庫中,因此技術上我可以從shutdownhook調用canExit。在關機時,我至少有機會備份尚未發送的通知,所以即使很可能只有一個或兩個未處理的項目在池中,我也不會丟失它們過程,那就是我以前的做法,當有人關閉Tomcat時處理最後一個或兩個通知。 –

回答

2

您可以使用addShutdownHook來捕獲進程終止事件並在那裏等待池。

例如:

Runtime.getRuntime().addShutdownHook(new Thread() { 
     public void run() { 
     NotificationService service = getInstance(); 
     service.executorService.shutdown(); 
     service.executorService.awaitTermination(30, TimeUnit.SECONDS); 
     } 
    }); 
+0

'運行時加addShutdownHook'正是我所期待的,謝謝@Shloim! –

1

這裏回答:Java Finalize method call when close the application 終結默認情況下不和做這個功能在退出運行已被棄用。

一個共同的建議是使用Runtime.addShutdownHook但要注意以下在線文檔的:

關閉掛鉤還應該迅速完成其工作。當一個程序調用退出時,期望虛擬機會立即關閉並退出。當由於用戶註銷或系統關閉而導致虛擬機終止時,底層操作系統可能只允許關閉並退出的固定時間。因此,嘗試任何用戶交互或在關閉鉤子中執行長時間運行計算是不可取的。

誠實地說,確保所有事情得到適當清理的最好方法是擁有自己的應用程序生命週期,您甚至可以在要求VM退出之前結束自己的應用程序生命週期。

+0

不幸的是,我對應用程序生命週期沒有任何控制權,它是一個遺留系統,我只是爲它建立一個庫。 shutdownhook可以完成任務,當時我無法處理的所有內容,我只會轉儲到隊列或數據庫中,但至少在關閉掛鉤時,我有機會在丟失任何數據之前清理隊列。 –

1

不要在庫中使用阻塞關閉鉤子或任何類似的東西。你永遠不知道圖書館是如何被使用的。所以它應該總是取決於使用你的庫的代碼在關閉時採取明智的行動。

當然,你必須提供必要的API,例如,加入生命週期的方法到類:

public class NotificationService { 
    ... 

    public void start() { 
     ... 
    } 

    /** 
    * Stops this notification service and waits until 
    * all notifications have been processed, or a timeout occurs. 
    * @return the list of unprocessed notification (in case of a timeout), 
       or an empty list. 
    */ 
    public List<Notification> stop(long timeout, TimeUnit unit) { 
     service.shutdown(); 
     if (!service.awaitTermination(timeout, unit)) { 
      List<Runnable> tasks = service.shutdownNow(); 
      return extractNotification(tasks); 
     } 
     return Collections.emptyList(); 
    } 

    private List<Notification> extractNotification(List<Runnable> tasks) { 
     ... 
    } 
} 

然後,應用程序代碼可以採取必要的行動來處理你的服務,例如:

public static void main(String[] args) { 
    NotificationService service = new NotificationService(...); 
    service.start(); 
    try { 
     // use service here 
    } finally { 
     List<Notification> pending = service.stop(30, TimeUnit.SECONDS); 
     if (!pending.isEmpty()) { 
      // timeout occured => handle pending notifications 
     } 
    } 
} 

順便說一句:避免使用單身人士,如果可行的話。

+0

幸運的是,在這種情況下,我確切知道庫將如何使用,並且將使用它的代碼(遺留代碼掛鉤到現在將調用我的庫的傳統方法中)不會調用手動關閉,因此關閉在這種情況下,鉤子是我能做的最好的。單身人士怎麼了?在這種情況下,由於執行通知所需的資源(遺留代碼)非常大,因此只有一個實例非常好,因此單例運行效果很好。這是Threadsafe,所以一個實例IMO應該沒問題。 –

+0

@JanVladimirMostert如果遺留代碼不支持生命週期處理的任何方式,那麼shutdownhook當然是您最後的(也是唯一的)解決方案。儘管如此,你可以嘗試在沒有鉤子的情況下實現你的服務,並且有一些安裝鉤子的「醜陋的包裝器」或者「醜陋的初始化代碼」。這使得測試更容易,例如你可以編寫單元測試而不用擔心鉤子。 – isnot2bad

+0

@JanVladimirMostert谷歌「單身是邪惡的」。可能不適合你的情況。一個原因是可測試性(再次):使用單例服務的代碼很難測試,因爲您不能簡單地使用服務存根。但我不得不承認:有時使用單例比任何其他(複雜)解決方案更容易和直接。 – isnot2bad