java線程池關閉
Ⅰ java銷毀線程池
你是用Java自帶的ExecutorService這個線程池么?如果是的話,ExecutorService自身會管理池中的線程,不需外界手動干預。
如果是自己寫的話,必須在線程池內的線程加入退出判定,要不然的話是沒法從線程外結束線程的。(你也不應該這樣做,因為這樣會導致驗證的資源泄漏)
Ⅱ 使用線程池時一定要注意的五個點
很多場景下應用程序必須能夠處理一系列傳入請求,簡單的處理方式是通過一個線程順序的處理這些請求,如下圖:
單線程策略的優勢和劣勢都非常明顯:
優勢:設計和實現簡單;劣勢:這種方式會帶來處理效率的問題,單線程的處理能力是有限,不能發揮多核處理器優勢。
在這種場景下我們就需要考慮並發,一個簡單的並發策略就是Thread-Per-Message模式,即為每個請求使用一個新的線程。
Thread-Per-Message策略的優勢和劣勢也非常明顯:
優勢:設計和實現比較簡單,能夠同時處理多個請求,提升響應效率;
劣勢:主要在兩個方面
1.資源消耗 引入了在串列執行中所沒有的開銷,包括線程創建和調度,任務處理,資源分配和回收以及頻繁上下文切換所需的時間和資源。2.安全
有沒有一種方式可以並發執行又可以克服Thread-Per-Message的問題?
採用線程池的策略,線程池通過控制並發執行的工作線程的最大數量來解決Thread-Per-Message帶來的問題。可見下圖,請求來臨時先放入線程池的隊列
線程池可以接受一個Runnable或Callable<T>任務,並將其存儲在臨時隊列中,當有空閑線程時可以從隊列中拿到一個任務並執行。
反例(使用 Thread-Per-Message 策略)
正例(使用 線程池 策略)
JAVA 中(JDK 1.5+)線程池的種類:
程序不能使用來自有界線程池的線程來執行依賴於線程池中其他任務的任務。
有兩個場景:
要緩解上面兩個場景產生的問題有兩個簡單的辦法:
真正解決此類方法還是需要梳理線程池執行業務流程,不要在有界線程池中執行相互依賴的任務,防止出現競爭和死鎖。
向線程池提交的任務需要支持中斷。從而保證線程可以中斷,線程池可以關閉。線程池支持 java.util.concurrent.ExecutorService.shutdownNow() 方法,該方法嘗試停止所有正在執行的任務,停止等待任務的處理,並返回等待執行的任務的列表。
但是 shutdownNow() 除了盡力嘗試停止處理主動執行的任務之外不能保證一定能夠停止。例如,典型的實現是通過Thread.interrupt()來停止,因此任何未能響應中斷的任務可能永遠不會終止,也就造成線程池無法真正的關閉。
反例:
正例:
線程池中的所有任務必須提供機制,如果它們異常終止,則需要通知應用程序.
如果不這樣做不會導致資源泄漏,但由於池中的線程仍然被會重復使用,使故障診斷非常困難或不可能。
在應用程序級別處理異常的最好方法是使用異常處理。異常處理可以執行診斷操作,清理和關閉Java虛擬機,或者只是記錄故障的詳細信息。
也就是說在線程池裡執行的任務也需要能夠拋出異常並被捕獲處理。
任務恢復或清除操作可以通過重寫 java.util.concurrent.ThreadPoolExecutor 類的 afterExecute() 鉤子來執行。
當任務通過執行其 run() 方法中的所有語句並且成功結束任務,或者由於異常而導致任務停止時,將調用此鉤子。
可以通過自定義 ThreadPoolExecutor 服務來重載 afterExecute()鉤子。
還可以通過重載 terminated() 方法來釋放線程池獲取的資源,就像一個finally塊。
反例:
任務意外終止時作為一個運行時異常,無法通知應用程序。此外,它缺乏恢復機制。因此,如果Task拋出一個NullPointerException ,異常將被忽略。
正例:
另外一種方式是使用 ExecutorService.submit() 方法(代替 execute() 方法)將任務提交到線程池並獲取 Future 對象。
當通過 ExecutorService.submit() 提交任務時,拋出的異常並未到達未捕獲的異常處理機制,因為拋出的異常被認為是返回狀態的一部分,因此被包裝在ExecutionException ,並由Future.get() 返回。
java.lang.ThreadLocal 類提供線程內的本地變數。根據Java API
ThreadLocal對象需要關注那些對象被線程池中的多個線程執行的類。
線程池緩存技術允許線程重用以減少線程創建開銷,或者當創建無限數量的線程時可以降低系統的可靠性。
當 ThreadLocal 對象在一個線程中被修改,隨後變得可重用時,在重用的線程上執行的下一個任務將能看到該線程上執行過的上一個任務修改的ThreadLocal 對象的狀態。
所以要在使用線程池時重新初始化的ThreadLocal對象實例。
反例:
DiaryPool類創建了一個線程池,它可以通過一個共享的無界的隊列來重用固定數量的線程。
在任何時候,不超過numOfThreads個線程正在處理任務。如果在所有線程都處於活動狀態時提交其他任務,則 它們在隊列中等待,直到線程可用。
當線程循環時,線程的線程局部狀態仍然存在。
下表顯示了可能的執行順序:
時間任務線程池提交方法日期1t11doSomething1()星期五2t22doSomething2()星期一3t31doSomething3()星期五
在這個執行順序中,期望從doSomething2() 開始的兩個任務( t 2和t 3 doSomething2() 將當天視為星 期一。然而,因為池線程1被重用,所以t 3觀察到星期五。
解決方案(try-finally條款)
符合規則的方案removeDay() 方法添加到Diary類,並在try‐finally 塊中的實現doSomething1() 類的doSomething1() 方法的語句。finally 塊通過刪除當前線程中的值來恢復threadlocal類型的days對象的初始狀態。
如果threadlocal變數再次被同一個線程讀取,它將使用initialValue()方法重新初始化 ,除非任務已經明確設置了變數的值。這個解決方案將維護的責任轉移到客戶端( DiaryPool ),但是當Diary類不能被修改時是一個好的選擇。
解決方案(beforeExecute())
使用一個自定義ThreadPoolExecutor 來擴展 ThreadPoolExecutor並覆蓋beforeExecute() 方法。beforeExecute() 方法在Runnable 任務在指定線程中執行之前被調用。該方法在線程 「t」 執行任務 「r」 之前重新初始化 threadlocal 變數。