從0學習java併發編程實戰-讀書筆記-取消與關閉(7)

要使任務和線程能安全、快速、可靠的停下來,並非一件容易的事。java沒有提供任何機制來安全地終止線程(Thread.stop和suspend等方法提供了這樣的功能,可是存在嚴重缺陷,應該避免使用)。
可是java提供了中斷(Interruption),這是一種協做機制,可以使一個線程終止另外一個線程的當前工做。
咱們不多但願某個任務、線程或服務當即中止,由於這種當即中止會使共享的數據結構處於不一致的狀態。通常使用的協做的方式:當須要中止時,它們首先會清除當前正在執行的工做,而後再結束。由於任務自己的代碼比發出取消請求的代碼更清除如何執行清除工做。java

任務取消

若是外部代碼能夠在某個操做正常完成以前,將其置入完成狀態,那麼這個操做就能夠稱爲可取消的。取消某個操做的緣由有不少:編程

  • 用戶取消操做:用戶點擊圖形界面上的取消按鈕,或者經過管理接口發送取消請求。
  • 有時間限制的操做:應用程序須要在有限的時間內選擇最佳的解決方案並解決問題。當計時器超時,須要取消全部正在執行的任務。
  • 應用程序事件:例如應用程序對某個問題空間進行搜索,不一樣的任務能夠搜索問題空間中的不一樣區域。當一個任務找到了解決方案時,其餘仍在搜索的任務將被取消。
  • 錯誤:例如當一個爬蟲想要將頁面數據保存到硬盤時,發生了錯誤(例如磁盤空間滿了),那麼全部的搜索任務都被取消,此時可能會記錄下狀態,以便以後從新啓動。
  • 關閉:當一個程序或服務關閉時,必須對正在處理和等待處理的工做執行某種操做。在平緩的關閉過程當中,當前任務可能被取消。

協做機制能設置某個已請求取消(Cancellation Request)標誌,而任務將按期查看這個標誌。若是設置了這個標誌,任務就提早結束。api

private volatile boolean cancelled;

public void run(){
    while(!cancelled){
        doSomething();
    }
}

public void cancel(){ cancelled = true; }

一個可取消的任務必須擁有取消策略(Cancellation Policy),這個策略中必須定義:其餘代碼如何How請求該任務,任務在什麼時候When檢查是否已經請求了取消,以及在響應取消時應該執行哪些What操做.緩存

中斷

若是在使用中斷某一個任務調用了一個阻塞方法,例如BlockingQueue.put,那麼可能會產生一個更嚴重的問題:任務可能永遠不會檢查取消標誌,所以永遠也不會結束。安全

private final BlockingQueue<T> queue;
private volatile boolean cancelled;

public void run(){
    while(!cancelled){
        queue.put(something);
    }
}

public void cancel(){ cancelled = true; }

當前線程爲生產者,當生產者在queue.put()方法上阻塞了,而這時候消費者但願取消生產者任務,執行了cancel()方法,可是生產阻塞在put上,也許永遠也沒有機會檢查cancelled標誌(若是消費者中止從隊列中取數,put方法就會一直阻塞)。服務器

在java的api或語言規範中,並無將中斷與任何取消語義關聯起來,但實際上,若是在取消以外的其餘操做中使用中斷,那麼都是不合適的,而且很難支撐起更大的應用。
// Thread中的中斷方法   
public class Thread{
    public void interrupt(){}
    public void isInterrupted(){}
    public static boolean interrupted(){}
}

阻塞庫方法(例如Thread.sleep()和Object.wait())等,都會檢查線程什麼時候中斷,而且在發現中斷時提早返回。
它們在響應中斷時執行的操做包括:數據結構

  • 清除中斷狀態
  • 拋出InterruptedException

JVM並不能保證阻塞方法檢測到中斷的速度(實際上速度是很快的)。併發

調用interrupt並不意味着當即中止目標線程正在進行的工做,而只是傳遞了請求中斷的消息。
對中斷操做的正確理解是:它並不會真正的中斷一個正在運行的線程,只是發出中斷請求,而後等待線程在下一個合適的時刻中斷本身(這些時刻也被稱爲取消點)。
一些自定義的取消機制沒法與可阻塞的庫函數實現良好交互。若是代碼可以響應中斷,那麼能夠用中斷做爲取消機制,而且利用許多類庫提供的中斷支持。
一般中斷是實現取消的最合理方式。
private final BlockingQueue<T> queue;

public void run(){
    try{
        while(!Thread.currentThread().isInterrupted()){
        queue.put(something);
        }
    }catch(InterruptedException consumed){
        // 我的理解:中斷線程也許是靠拋出異常來退出的
    }

    public void cancel(){ interrupted(); }
}

中斷策略

中斷策略規定線程如何解釋中斷請求:當發現中斷時,應該作哪些工做,哪些工做單元對中斷來講是原子操做,以及以多快的速度來響應中斷。
最合理的中斷策略是某種形式的線程級(Thread-Level)取消操做或是服務級(Service-Level)取消操做:框架

  • 儘快退出
  • 在必要時清理
  • 通知某個全部者線程該線程已退出。

一箇中斷請求可能有一個或者多個接受者,中斷線程池中的某個工做者線程,同時意味着「取消當前任務」和「關閉工做者線程」。
阻塞的庫函數都只是拋出InterruptedException做爲中斷響應,它們的取消策略:儘快退出執行流程,並把中斷信息傳遞給調用者,從而使使用棧中的上層代碼能夠採起進一步操做。異步

因爲每一個線程擁有各自的中斷策略,所以除非你知道中斷對該線程的含義,不然就不該該中斷該線程。

響應中斷

當調用可中斷的阻塞函數時(例如Thread.sleep或BlockingQueue.put等),有兩種實用策略可用於處理InterruptedException:

  • 傳遞異常(可能在執行某個特定於任務的清除操做以後),從而使你的方法也成爲可中斷的阻塞方法。
  • 恢復中斷狀態,從而使調用棧中的上層代碼可以對其進行處理。
/**
 * 傳遞異常的方法,能夠直接經過throws拋出
**/
BlockingQueue<Task> queue;
……
public Task getNextTask() throws InterruptedException {
    return queue.take();
}

若是不想或者不能傳遞InterruptedException,那麼要尋找另外一種方式來保存中斷請求。一種標準的作法就是經過再次調用interrupt來恢復中斷狀態。除非在代碼中實現了中斷策略,不然不要無視InterruptedException。

只有實現線程中斷策略的代碼才能夠屏蔽中斷請求。在常規的任務和庫代碼中都不該該屏蔽中斷請求。
對於一些不支持取消,可是仍然能夠調用中斷阻塞方法的操做,它們必須在循環中調用這些方法,並在發現中斷後從新嘗試。

不可取消的任務在退出前恢復中斷

boolean interrupted = false;
try{
    while(true){
        try{

        }catch(InterruptedException e){
            interrupted = true;
            // 從新嘗試
        }
    }
}finally{
    if(interrupted){
        Thread.currentThread().interrupt();
    }
}

若是代碼不會調用可中斷的阻塞方法,那麼能夠經過在任務代碼中輪詢當前線程的中斷狀態來響應中斷。
中斷能夠用來獲取線程的注意,而且由中斷線程保存的信息,能夠爲中斷的線程提供進一步的提示(訪問這些信息的時候須要確保使用同步)。

計時運行

private static final ScheduledExcutorService cancelExec = ...

private static void timedRun(Runnable r, long timeout, TimeUnit unit){
    final Thread taskThread = Thread.currentThread();
    cancelExec.schedule(new Runnable(){
        public void run(){
            taskThread.interrupt();
        }
    },timeout unit);
    r.run();
}

這是一個在指定時間內運行一個任意的Runnable的示例。它在調用線程中的運行任務,並安排了一個取消任務,在運行了指定的時間間隔後中斷它。這解決了從任務中拋出未檢查異常的問題,由於這個異常會被timeRun()的調用者所捕獲。
可是問題是:在中斷以前,應該瞭解它的中斷策略。
由於timeRun()能夠從任意一個線程中調用,所以它沒法得知這個調用線程的中斷策略。若是任務在超時前完成,那麼中斷timeRun所在線程的取消任務將愛timedRun返回到調用者以後啓動。並且若是任務不響應中斷,那麼timeRun將會在任務結束時才返回,有可能超過的調用者所指定的時限。

在專門的線程中中斷任務

public static void timedRun(final Runnable r, long timeout, TimeUnit unit) throws InerruptedException {
    class RethrowableTask implements Runnable {
        private volatile Throwable t;
        public void run() {
            try{
                r.run();
            }catch(Throwable t){
                this.t = t;
            }
        }

        void rethrow(){
            if(t != null){
                throw launderThrowable(t);
            }
        }

    }

    RethrowableTask task = new RethrowableTask;
    final Thread taskThread = new Thread(task);
    taskThread.start();
    cancelExec.schedule(new Runnable(){
        public void run(){
            taskThread.interrupt();
        }
    }, timeout, unit);
    taskThread.join(unit.toMillis(timeout));
    task.rethrow();
}

執行任務的線程擁有本身的執行策略,即便任務不響應中斷,即時運行的方法仍能返回到它的調用者。
在啓動任務線程以後,timeRun將執行一個限時的join方法。在join返回後,它將檢查任務中是否有異常拋出,若是有的話,則會在調用timedRun的線程中再次拋出該異常。因爲Throwable將在兩個線程之間共享,所以設置爲volatile類型,來保證安全發佈。
可是該代碼依賴一個限時的join,所以有着join的不足:沒法知道執行控制是由於線程正常退出而返回,仍是由於join超時而返回。

經過Future來實現取消

ExecutorService.submit()將返回一個Future來描述任務。Future擁有一個cancel方法,該方法帶有一個boolean類型的參數mayInterruptIfRunning,表示取消操做是否成功(只是表示可否接受中斷,而不是表示任務是否能檢測並處理中斷)。

  • 若是mayInterruptIfRunning爲true且任務當前正在某個線程運行,那麼這個線程能夠被中斷。
  • 若是mayInterruptIfRunning爲false,那麼意味着若任務還沒啓動,則不要運行它

執行任務的線程是由標準的Executor建立的,它實現了一種中斷策略使得任務能夠經過中斷被取消,若是任務在標準Executor中運行,並經過它們的Future來取消任務,那麼能夠設置mayInterruptIfRunning。當嘗試取消某個任務時,不宜直接中斷線程池,由於你不知道當中斷請求到達時線程正在運行什麼任務,只能經過任務的Future來實現。

public static void timedRun(Runnable r, long timeout, TimeUnit unit) throws InterruptedException {
    Future<?> task = taskExec.submit(r);
    try{
        task.get(timeout,unit);
    }catch(TimeoutException e){
        // 接下來任務將被取消
    }catch(ExecutionException e){
        // 若是任務中拋出了異常,則從新拋出該異常
        throw launderThrowable(e.getCause());
    }finally {
        // 若是任務已經結束,取消操做也不會帶來任何影響
        // 若是任務正在運行,那麼將被中斷
        task.cancel();
    }
}
Future.get拋出 InterruptedExceptionTimeoutException時,若是你知道再也不須要結果,那麼就能夠調用 Future.cancel來取消任務。

處理不可中斷的阻塞

在java庫,許多可阻塞的方法都是經過提早返回或者拋出InterruptedException來響應中斷請求的。然而並不是全部的可阻塞方法或者阻塞機制都能響應中斷。
若是一個線程因爲執行同步的Socket I/O或者等待獲取內置鎖而阻塞,那麼中斷請求只能設置線程的中斷狀態,除此以外沒有其餘做用。

線程阻塞的緣由

  • java.io包中的同步Socket I/O

    • 在服務器應用程序中,最多見的阻塞I/O就是對套接字進行讀取和寫入。雖然InputStreamOutputStreamread和write方法不會響應中斷,但經過關閉底層的套接字,可使因爲執行read和write等方法被阻塞的線程拋出一個SocketException.
  • java.io包中的同步I/O

    • 中斷一個正在InterruptibaleChannel上等待的線程時,將拋出ClosedByInterruptException並關閉鏈路(會使得其餘在這條鏈路上阻塞的線程通用拋出ClosedByInterruptException)。
    • 關閉一個InterruptibaleChannel,將致使全部在鏈路操做上阻塞的線程都拋出AsynchronousCloseException。大多數標準的Channel都實現了InterruptibaleChannel
  • Selector的異步I/O

    • 若是一個線程正在調用java.nio.channels中的Select.select方法阻塞了,那麼調用close或者wakeup方法會使線程拋出ClosedSelectorException並提早返回。
  • 得到某個鎖:

    • 若是一個線程因爲等待某個內置鎖而阻塞,那麼將沒法響應中斷,由於線程認爲它確定能得到到鎖,因此不會理會中斷請求。(可是在Lock類中提供了lockInterruptibly方法,該方法容許在等待一個鎖的同時能響應中斷)。
public class ReaderThread extends Thread{
    private final Socket socket;
    private final InputStream inputStream;
    
    public ReaderThread(Socket socket) throws IOException{
        this.socket = socket;
        this.inputStream = socket.getInputStream();
    }
    
    public void interrupt(){
        try {
            socket.close();
        }catch (IOException ignored){
            
        }finally {
            super.interrupt();
        }
    }
    
    public void run(){
        try {
            byte[] buf = new byte[1000];
            while (true){
                int count = inputStream.read(buf);
                if(count<0){
                    break;
                }else if (count>0){
                    doSomething(buf,count);
                }
            }
        }catch (IOException e){

        }
    }
}

經過改寫interrupt方法,既能處理標準的中斷,也能關閉底層套接字。不管線程是在read或write方法中阻塞仍是在某個可中斷方法中阻塞,均可以被中斷中止執行當前工做。

採用newTaskFor來封裝非標準的取消

咱們能夠經過newTaskFor方法是ThreadPoolExecutor中的新增功能。當把一個Callable提交給ExecutorService時,submit()會返回一個Future,咱們能夠經過這個Future來取消任務。newTaskFor是一個工廠方法,它將建立Future來表明任務。newTaskFor還能返回一個RunnableFuture接口,該接口拓展了FutureRunnable(由FutureTask實現)。
經過定製表示任務的Future能夠改變Future.cancel的行爲。例如,定製的取消代碼能夠實現日誌記錄或者收集取消操做的統計信息,以及取消一些不響應中斷的操做。

public interface CancellableTask<T> extends Callable<T> {
    void cancel();
    RunnableFuture<T> newTask();
}

public class CancellingExecutor extends ThreadPoolExecutor{
    ...
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        if (callable instanceof CancellableTask){
            return ((CancellableTask<T>) callable).newTask();
        }else {
            return super.newTaskFor(callable);
        }
    }

    public abstract class SocketUsingTask<T> implements CancellableTask<T> {
        private Socket socket;

        protected synchronized void setSocket(Socket s){
            socket = s;
        }

        public synchronized void cancel(){
            try{
                if(socket != null){
                    socket.close();
                }
            }catch(IOException ignored){}
        }

        public RunnableFuture<T> newTask(){
            return new FutureTask<T>(this){
                public boolean cancel(boolean mayInterruptIfRunning){
                    try{
                        SocketUsingTask.this.cancel();
                    }finally{
                        return super.cancel(mayInterruptIfRunning);
                    }
                }
            }
        }
    }
}

CancellableTask中定義了一個CancellableTask接口,該接口拓展了Callable,並增長了一個cancel方法和一個newTask工廠方法來構造RunnableFuture。CancellingExecutor拓展了ThreadPoolExecutor,並改寫了newTaskFor使Cancellable能夠建立本身的Future。

中止基於線程的服務

正確的封裝原則:除非擁有某個線程,不然不能對該線程進行操控。
線程由Thread對象表示,而且像其餘對象同樣能夠被自由的共享。可是線程有一個相應的全部者,即建立該線程的類。所以線程池是其工做線程的全部者,若是要中斷這些線程,那麼應該使用線程池。
與其餘封裝對象同樣,線程的全部權是不可傳遞的:應用程序能夠擁有服務,服務也能夠擁有工做者線程,可是應用程序並不能擁有工做者線程,所以應用程序不能直接中止工做者線程。應用程序應該提供生命週期方法(Lifecycle Method)來關閉它本身以及它所擁有的線程。在ExecutorService中提供了shutdown和shutdownNow等方法,在其餘擁有線程的服務中也應該提供相似的關閉機制。

對於持有線程的服務,只要服務的存在時間大於建立線程的方法的存在時間,那就應該提供生命週期方法。

關閉ExecutorService

ExecutorService提供了兩種關閉方法:

  • 使用shutdown正常關閉:速度相對shutdownNow來講更慢,安全性高,會等待隊列中全部的任務都執行完才關閉。
  • 使用shutdownNow強行關閉:速度快,可是安全性低,首先關閉當前正在執行的任務,而後返回未啓動的任務清單。

「毒丸」對象

一種關閉生產者-消費者服務的方式就是使用毒丸(Poison Pill)對象:毒丸是指一種放在隊列上的對象,其含義是:當獲得這個對象,當即中止。在FIFO隊列中,毒丸對象將會確保消費者在關閉以前首先完成隊列中的全部工做,再提交毒丸。毒丸對象以前的全部工做都會獲得處理,而生產者在提交毒丸對象之後,將不會提交任何的工做。

當生產者和消費者的數量較大時,這種方法將變得難以使用。只有在無界隊列中,毒丸對象才能可靠的工做。

shutdownNow的侷限性

當經過shutdownNow來強行關閉ExecutorService時,它會嘗試取消正在執行的任務而且返回已提交可是還沒有執 行的任務(以便調用者線程把這些任務寫入日誌或者作其餘後續處理);shutdownNow返回的List<Runnable>可能與提交給ExecutorService的Runnable不一樣,它們可能被封裝或者修改過。
可是咱們沒法經過普通方法找出哪些任務已經開始可是還沒有結束,咱們沒法得知狀態,除非執行線程中有某些檢查。

處理非正常的線程終止

致使線程提早死亡的最主要緣由就是RuntimeException,因爲某些異常表示了某種編程錯誤或其餘相似的不可修復的錯誤,所以它們不會被捕獲。它們不會在調用棧中逐層傳遞,而是默認的在控制檯輸出棧追蹤信息,並終止線程。
任何代碼均可能拋出一個RuntimeException。每當調用另外一個方法時,都要對它的行爲保持懷疑,不要盲目的認爲它必定會正常返回,或者必定會拋出在方法原型中聲明的異常。對調用的代碼越不熟悉,越應該對其行爲保持懷疑。
在任務處理線程的生命週期中,將經過某種抽象機制(如Runnable)來調用許多未知的代碼,咱們應該對這些線程可否表現出正確的行爲表示懷疑。所以,這些線程應該在try-catch代碼塊中調用這些任務,就能捕獲未檢測的異常了,或者也可使用try-finally代碼塊來確保框架可以知道線程非正常退出的狀況。

public void run(){
    Throwable thrown = null;
    try{
        while(!isInterrupted){
            runTask(getTaskFromWorkQueue());
        } catch (Throwable e){
            thrown = e;
        } finally {
            threadExited(this,thrown);
        }
    }
}

在線程池內部構建一個工做者線程,若是任務拋出了一個未檢查異常,那麼它將使線程終結,可是會先同時框架它已經終結。而後框架可能會調用一個新的線程來代替這個工做線程,也可能不會,由於線程池正在關閉,或者已經有足夠多的線程能知足須要。當使用這種方法,能夠避免某個編寫的糟糕的任務或插件時不會影響調用它的整個線程。

未捕獲異常的處理

在Thread API中一樣提供了UncaughtExceptionHandler,它能檢測某個線程因爲捕獲異常而終結的狀況。這個與前面的工做者線程是互補的,經過將兩者結合在一塊兒,能夠有效的防止線程泄露問題。

public interface UncaughtExceptionHandler {
    void uncaughtExceptionHandle(Thread t, Throwable e);
}

異常處理器如何捕獲異常,取決於對服務質量的需求。最多見的響應方式將一個錯誤信息以及相應的棧追蹤信息寫入應用程序日誌中。

public class UEHLogger implements Thread.UncaughtExceptionHandler {
    public void uncaughtExceptionHandler(Thread t, Throwable e){
        Logger logger = Logger.getAnonymousLogger();
        logger.log(Level.SEVERE, "Thread terminated with Exception:" + t.getName(), e);
    }
}

能夠經過Thread.setUncaughtExceptionHandler爲每一個線程設置一個UncaughtExceptionHandler,還可使用setDefaultUncaughtExceptionHandler來設置默認的UncaughtExceptionHandler。

在運行時間較長的應用程序中,一般會爲全部線程的未捕獲異常指定同一個異常處理器,而且該處理器至少會將異常信息記錄到日誌中。
若是要爲線程池中的全部線程設置一個UncaughtExceptionHandler,須要爲ThreadPoolExecutor的構造函數提供一個ThreadFactory。標準線程池容許當發送未捕獲異常時結束線程,但因爲使用了一個try-finally代碼塊來接受其餘通知,所以當線程結束時,將有新的線程來代替它。若是沒有提供捕獲異常處理器或其餘的故障通知機制,那麼任務會悄悄的失敗,從而形成極大的混亂。若是你但願任務因爲發生異常而失敗的時得到通知,而且執行一些特定於任務的恢復操做,那麼能夠將 任務封裝在能捕獲異常的Runnable或Callable中,或者 改寫ThreadPoolExecutor的afterExecute方法
可是隻有經過execute提交的任務,才能將異常交給捕獲異常處理器,而經過submit提交的任務,不管是拋出的未檢查異常仍是已檢查異常,都將被認爲是任務返回狀態的一部分。若是一個由submit提交的任務拋出異常,那麼將被Future.get封裝在ExecutionException中從新拋出。

JVM關閉

JVM既能夠正常關閉,也能夠強行關閉。
正常關閉的觸發方式:

  • 當最後一個「正常(非守護)」線程結束
  • 當調用了System.exit
  • 經過特色於平臺的方法關閉(例如發送給你SIGINT信號,CTRL-C等)

也能夠經過調用Runtime.halt或者在操做系統中發送SIGKILL等

關閉鉤子

在正常關閉中,JVM首先調用全部已註冊的關閉鉤子(Shutdown Hook)。關閉鉤子是指經過Runtime.addShutdownHook註冊的但還沒有開始的線程。

  • JVM並不能保證關閉鉤子的順序。
  • 在關閉應用程序時,若是有線程(不管是否是守護線程)仍在運行,那麼這些線程將與關閉進程併發執行。
  • 當全部鉤子都執行結束時,若是runFinalizersOnExit爲true,那麼JVM將運行終結器,而後中止。
  • JVM並不會中止或中斷任何在關閉時仍然運行的應用程序線程。
  • 當JVM最終結束時,這些線程將被強行結束。
  • 若是關閉鉤子或終結器沒有執行完成,那麼正常關閉進程「掛起」,而且JVM必須被強行關閉。當被強行關閉時,只是關閉JVM,而不會關閉鉤子。

關閉鉤子應該是線程安全的:

  • 它們在訪問共享數據時必須使用同步機制,而且當心的避免發生死鎖。
  • 並且關閉鉤子不該該對應用程序狀態(如其餘服務是否關閉,全部正常線程是否已經執行完成等)作出任何假設,所以關閉鉤子的代碼必須考慮周全。
  • 關閉鉤子必須儘快退出,由於它們會延遲JVM的結束時間,而用戶可能但願JVM能儘快終止。

關閉鉤子能夠用於實現服務或應用程序的清理工做,例如刪除臨時文件,或者清除沒法由系統自動清除的資源。
因爲關閉鉤子將併發執行,所以在關閉日誌文件時可能致使其餘須要日誌服務的關閉鉤子產生問題:所以,關閉鉤子不該該依賴於那些可能被應用程序或其餘的關閉鉤子關閉的服務。實現這種功能的一種方式是對全部服務使用同一個關閉鉤子,而且在該關閉鉤子中執行一些列的關閉操做。這確保了關閉操做在單個線程中串行執行,從而避免了操做之間出現競態條件或死鎖等問題。

public void start(){
    Runtime.getRuntime().addShutdownHook(new Thread(){
        public void run(){
            try{
                LogService.this.stop();
            }catch(InterruptedException ignored){}
        }
    });
}

守護線程

有時候你但願建立一個線程來執行一些輔助工做,可是又不但願這個線程阻礙JVM的關閉,那麼你可使用守護線程(Daemon Thread)

線程分爲守護線程和普通線程。在JVM啓動時建立的全部線程,除了主線程,其餘都是守護線程(例如GC或其餘輔助工做的線程)。
當建立一個新線程時,新線程將繼承建立它的線程的守護狀態。所以主線程建立的都是普通線程。

守護線程和普通線程的區別

僅在於線程退出時發生的操做:當一個線程退出時,JVM會檢查其餘正在運行的線程,若是這些線程都是守護線程,那麼JVM會正常的退出操做。當JVM中止時,全部仍然存在的守護線程將被直接拋棄:不會執行finally代碼塊,也不會執行回捲棧,JVM會直接退出。

咱們應該儘可能不使用守護線程 : 由於不多有操做可以在不進行清理的狀況下被安全地拋棄。例如若是在守護線程中執行包含I/O操做的任務,那麼將是一種危險的行爲。守護線程最好用於執行「內部」任務,例如週期性的從緩存中移除無效數據。

此外,守護線程一般不能用來替代應用程序管理程序中的各個服務的生命週期。

終結器

當再也不須要內存資源的時候,能夠經過GC自動回收它們。對於一些其它資源,如文件句柄或者套接字句柄,當再也不須要它們的時候,須要顯式的還給操做系統。爲了實現這個功能,垃圾回收期對定義了finalize方法的對象會進行特殊處理:在回收期釋放它們之後,調用它們的finalize方法,從而保證一些持久化的資源被釋放。
因爲終結器能夠在某個由jvm管理的線程中運行,所以終結器訪問的任何狀態均可能被多個線程訪問,這樣就必須對其訪問操做進行同步。
終結器也沒法保證它們在什麼時候運行甚至是否運行,而且複雜的終結器將在對象上產生巨大的性能開銷。
在大多數時候,使用finally代碼塊和顯式的close方法,可以比使用終結器更好地管理資源。
惟一的例外狀況是:當須要管理對象,而且該對象持有的資源是經過本地方法得到的。

避免使用終結器。

小結

在任務,線程,服務以及應用程序等模塊中的生命週期結束問題,可能會增長它們在設計和實現時的複雜性。java並無提供某種搶佔式的機制來取消或者終結線程。相反,它提供了一種協做式的中斷機制來實現取消操做,但這要依賴於如何構建取消操做的協議,以及可否始終遵循這些協議。經過FutureTask和Executor框架,能夠幫助咱們構建可取消任務和服務。

相關文章
相關標籤/搜索