1. 程式人生 > 其它 >多執行緒中如何進行異常處理?

多執行緒中如何進行異常處理?

一、Thread的預設異常處理

執行緒不允許丟擲未捕獲的checked exception(比如sleep時的InterruptedException),也就是說各個執行緒需要自己把自己的checked exception處理掉。我們可以檢視一下Thread類的run()方法宣告,方法宣告上沒有對丟擲異常進行任何約束。

    //Thread類中
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
    //Runnable介面中
    public abstract void run();

JVM的這種設計源自於這樣一種理念:“執行緒是獨立執行的程式碼片斷,執行緒的問題應該由執行緒自己來解決,而不要委託到外部。”基於這樣的設計理念,在Java中,執行緒方法的異常(無論是checked exception還是unchecked exception),都應該線上程程式碼邊界之內(run方法內)進行try catch並處理掉。換句話說,我們不能捕獲從執行緒中逃逸的異常。

二、未捕獲的異常如何處理的

一個異常被丟擲後,如果沒有被捕獲處理,則會一直向上拋。異常一旦被Thread.run() 丟擲後,就不能在程式中對異常進行捕獲,最終只能由JVM捕獲。

package com.joshua317;

public class ThreadException {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run()
    {
        System.out.println("執行緒名:" + Thread.currentThread().getName());

        //發生異常
        int i = 1 / 0;

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

接下來我們嘗試對異常進行捕獲,看看是否能捕獲到

package com.joshua317;

public class ThreadException {
    public static void main(String[] args) {
        try {
            MyThread myThread = new MyThread();
            myThread.start();
        }catch (Exception e) {
            System.out.println("捕獲執行緒丟擲的異常!" + e.getMessage());
        }

    }
}

class MyThread extends Thread {
    @Override
    public void run()
    {
        System.out.println("執行緒名:" + Thread.currentThread().getName());

        //發生異常
        int i = 1 / 0;

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

結果我們發現,我們嘗試在main方法中對執行緒中丟擲的異常進行捕獲,但是毫無作用。

三、那麼,JVM如何處理執行緒中丟擲的異常的呢

檢視Thread類的原始碼,我們可以看到有個dispatchUncaughtException方法,此方法就是用來處理執行緒中丟擲的異常的。JVM會呼叫dispatchUncaughtException方法來尋找異常處理器(UncaughtExceptionHandler),處理異常。

    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
     //向handler分派未捕獲的異常。該方法僅由JVM呼叫。
    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
    /**
     * Returns the handler invoked when this thread abruptly terminates
     * due to an uncaught exception. If this thread has not had an
     * uncaught exception handler explicitly set then this thread's
     * <tt>ThreadGroup</tt> object is returned, unless this thread
     * has terminated, in which case <tt>null</tt> is returned.
     * @since 1.5
     * @return the uncaught exception handler for this thread
     */
    // 獲取用來處理未捕獲異常的handler,如果沒有設定則返回當前執行緒所屬的ThreadGroup
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

UncaughtExceptionHandler必須顯示的設定,否則預設為null。若為null,則使用執行緒預設的handler,即該執行緒所屬的ThreadGroup。ThreadGroup自身就是一個handler,檢視ThreadGroup的原始碼就可以發現,ThreadGroup實現了Thread.UncaughtExceptionHandler介面,並實現了預設的處理方法。預設的未捕獲異常處理器處理時,會呼叫 System.err 進行輸出,也就是直接列印到控制檯了。

public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

由此可知,最終JVM是呼叫未捕獲的異常處理器的uncaughtException()方法來處理異常的,並且是直接列印到控制檯。

四、如何自定義處理執行緒異常

如果我們要自己處理異常,該怎麼辦呢?通過前面的分析,我們已經知道了執行緒會使用預設的未捕獲異常處理器來處理異常。自然我們可以想到,是否可以自定義未捕獲異常處理器,覆蓋掉預設的捕獲異常處理器。實際上,Thead確實已經提供了一個setUncaughtExceptionHandler方法,我們只需要將自定義未捕獲異常處理器作為引數傳入進入就可以了。

接下來我們自定義處理執行緒異常

package com.joshua317;

public class ThreadException {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setUncaughtExceptionHandler();
        myThread.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run()
    {
        System.out.println("執行緒名:" + Thread.currentThread().getName());

        //發生異常
        int i = 1 / 0;

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void setUncaughtExceptionHandler()
    {
        this.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("捕獲執行緒丟擲的異常:" + e.getMessage());
            }
        });
    }
}

五、執行緒池中自定義處理異常

要自定義處理異常,只需要為執行緒提供一個自定義的UncaughtExceptionHandler。而線上程池中,該如何批量的為所有執行緒設定UncaughtExceptionHandler呢?我們知道,執行緒池中的執行緒是由執行緒工廠建立的。我們可以跟蹤ThreadPoolExecutor構造方法的原始碼,最終定位到DefaultThreadFactory類,該類中有個newThread()方法,這就是執行緒產生的源頭了。

//ThreadPoolExecutor類中
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    
//Executors類中  
public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }
    

//DefaultThreadFactory類中
public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }

找到了執行緒建立的源頭,我們就可以實現一個自己的執行緒工廠,並覆蓋預設的newThread方法,為新建立的執行緒提供一個UncaughtExceptionHandler,即可達到我們的目的。由於DefaultThreadFactory是Executors類的內部類,我們不能直接繼承該類,只能實現該工廠類的介面——ThreadFactory介面,來實現我們自己的執行緒工廠。

package com.joshua317;

import java.util.concurrent.ThreadFactory;

public class MyThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread();
        //自定義UncaughtExceptionHandler
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        return t;
    }
}

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("捕獲執行緒丟擲的異常:" + e.getMessage());
    }
}