1. 程式人生 > >處理Callable執行緒內部的非受檢異常

處理Callable執行緒內部的非受檢異常

之前的部落格中介紹過Runnable類執行緒的異常處理,資料JAVA多執行緒的童鞋應該知道。Java中處理Thread和Runnable執行緒體系之外,還用著名的Executor和Callable執行緒體系。而且,後者在實際中更為常見。那麼在Runnable中奏效的UncaughtExceptionHandler機制在Callable中時候仍然有效呢?我們用程式碼來驗證一下。


import java.util.concurrent.*;

public class Demo {
    private static final ExecutorService executorService = Executors.newSingleThreadExecutor();

    public static void main(String[] args) {
        Future<Integer> future = executorService.submit(new MyTask());
        try {
            System.out.println("myTask任務執行結果為" + future.get());
        } catch (InterruptedException e) {
            System.out.println("任務被中斷!");
        } catch (ExecutionException e) {
            System.out.println("任務內部丟擲未受檢異常!");
        } catch (CancellationException e){
            System.out.println("任務被取消!");
        }
        executorService.shutdown();
    }

    private static final class MyTask implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {
                    System.out.println("unchecked exception happened:");
                    System.out.println(t.getId());
                    System.out.println(t.getName());
                    e.printStackTrace(System.out);
                }
            });
            int sum = 0;
            for (int i = 4; i >= 0; i--) {
                sum = sum + (12 / i);
            }
            return sum;
        }
    }

}

執行上面的程式發現,我們自定義的UncaughtExceptionHandler沒有生效。相反,Executor框架的異常捕獲機制倒是生效了。這說明Callable已經不再使用UncaughtExceptionHandler機制類處理非受檢異常了,而是使用它自己特有的機制。這一特有的機制是什麼呢?


線上程池中,執行緒池Executor會Catch住所有執行時異常,當使用Future.get()獲取其結果時,才會丟擲這些執行時異常。我們看Future類的get()方法就可見一斑。

    /**
     * Waits if necessary for the computation to complete, and then retrieves its result.
     * @return the computed result
     * @throws CancellationException if the computation was cancelled
     * @throws ExecutionException if the computation threw an exception
     * @throws InterruptedException if the current thread was interrupted while waiting
     */
    V get() throws InterruptedException, ExecutionException;
從JDK對Future.get()方法的定義可見:Callable執行緒中丟擲的非受檢異常會被Executor框架捕獲到,然後通過Future類的get()方法傳遞給呼叫者。get() 方法的ExecutionException異常就是Runnable執行緒或者Callable執行緒丟擲的異常。


那麼問題來了,使用Runnable結合Executor框架是否也是同樣的處理機制呢?還是說使用Runnable結合Executor就會使得UncaughtExceptionHandler生效呢?來看下面的程式碼:

import java.util.concurrent.*;
public class Demo {
    private static final ExecutorService executorService = Executors.newSingleThreadExecutor();

    public static void main(String[] args) {
        Future<?> future = executorService.submit(new MyTask());
        try {
            System.out.println("myTask任務執行結果為" + future.get());
        } catch (InterruptedException e) {
            System.out.println("任務被中斷!");
        } catch (ExecutionException e) {
            System.out.println("任務內部丟擲未受檢異常!");
        } catch (CancellationException e) {
            System.out.println("任務被取消!");
        }
        executorService.shutdown();
    }

    private static final class MyTask implements Runnable {
        @Override
        public void run() {
            Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {
                    System.out.println("unchecked exception happened:");
                    System.out.println(t.getId());
                    System.out.println(t.getName());
                    e.printStackTrace(System.out);
                }
            });
            int sum = 0;
            for (int i = 4; i >= 0; i--) {
                sum = sum + (12 / i);
            }
        }
    }
}

從程式執行的結果來看,Runnable結合Executor的情況下,仍然是和Callable的異常處理機制一樣。我們可以這樣總結: 當使用Executor執行緒池相關框架來執行執行緒任務時,UncaughtExceptionHandler執行緒異常處理機制就是不生效的,這種情況下,執行緒內部的異常由執行緒池框架統一管理。當程式呼叫Future類的get()方法時,將感知到執行緒內部的執行時異常。當程式不使用Future類獲取結果時,執行時異常將被執行緒池框架隱藏。看下面的程式碼:
import java.util.concurrent.*;

public class Demo {
    private static final ExecutorService executorService = Executors.newSingleThreadExecutor();

    public static void main(String[] args) {
        executorService.submit(new MyTask());

        executorService.shutdown();
    }

    private static final class MyTask implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {

            int sum = 0;
            for (int i = 4; I >= 0; i--) {
                sum = sum + (12 / i);
            }
            return sum;
        }
    }
}

此程式後,在控制檯沒有任何輸出。 可見不使用future.get()的時候,執行時異常被執行緒池框架給“吃了”。

既然是由執行緒池框架統一管理,那麼是夠可以修改修改執行緒池框架的異常處理機制呢?答案是可以的。此時我們要自定義執行緒池框架,使用ThreadPoolExecutor類即可。ThreadPoolExecutor類提供了很多可調整的引數和鉤子函式,可以很方便的構造一個執行緒池。具體介紹參見筆者之前的部落格ThreadPoolExecutor執行緒池的使用。在這篇部落格中,介紹了一個afterExecute(Runnable, Throwable)方法,在該方法中可以定義當前執行緒池中執行的執行緒發生內部異常時的處理方案。例子如下:

import java.util.concurrent.*;
public class Demo {
    private static final ExecutorService executorService = new ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10)) {
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
            printException(r, t);
        }

        private void printException(Runnable r, Throwable t) {

            if (t == null && r instanceof Future<?>) {
                try {
                    Object result = ((Future<?>) r).get();
                } catch (CancellationException ce) {
                    t = ce;
                } catch (ExecutionException ee) {
                    t = ee.getCause();
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt(); // ignore/reset
                }
            }
            if (t != null) {
                t.printStackTrace(System.out);
                executeTask();
            }
        }
    };


    public static void main(String[] args) throws InterruptedException {
        executeTask();
        executorService.awaitTermination(5, TimeUnit.SECONDS);
        executorService.shutdownNow();
    }

    private static void executeTask() {
        executorService.submit(new MyTask());
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static final class MyTask implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            int sum = 0;
            for (int i = 4; i >= 0; i--) {
                sum = sum + (12 / i);
            }
            return sum;
        }
    }
}

在上面的程式碼中,我們使用ThreadPoolExecutor類自定義了一個執行緒池物件,對於所有使用該執行緒池執行的任務,其異常都會通過afterExecute()方法捕獲到並進行處理。至此,關於Executor和Callable體系的執行緒內部執行時異常處理方案就介紹完了,你懂了嗎!