溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java線程異常結(jié)束的解決方法

發(fā)布時(shí)間:2020-09-08 11:39:56 來(lái)源:億速云 閱讀:401 作者:小新 欄目:編程語(yǔ)言

這篇文章將為大家詳細(xì)講解有關(guān)Java線程異常結(jié)束的解決方法,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

Java中線程異常結(jié)束的解決方法是:解決線程異常結(jié)束的關(guān)鍵是要捕獲線程執(zhí)行過(guò)程中所產(chǎn)生的異常,當(dāng)線程異常時(shí),會(huì)通過(guò)調(diào)用setUncaughtExceptionHandler方法來(lái)捕獲異常再解決

Java線程異常結(jié)束的解決方法

我們開發(fā)工程中經(jīng)常使用到線程,在線程使用上,我們可能會(huì)有這樣的場(chǎng)景:

(1)伴隨這一個(gè)業(yè)務(wù)產(chǎn)生一個(gè)比較耗時(shí)的任務(wù),而這個(gè)業(yè)務(wù)返回并不需要等待該任務(wù)。那我們往往會(huì)啟動(dòng)一個(gè)線程去完成這個(gè)異步任務(wù)。

(2)我們需要一個(gè)定時(shí)任務(wù)比如:定時(shí)清除數(shù)據(jù),我們會(huì)起一個(gè)定時(shí)執(zhí)行線程去做該任務(wù)。

上述問(wèn)題比較簡(jiǎn)單,new一個(gè)線程然后去做這件事。但是我們常常忽略一個(gè)問(wèn)題,線程異常了怎么辦?比如耗時(shí)任務(wù)我們只完成了一半,我們就異常結(jié)束了(這里不考慮事務(wù)一致性,我們只考慮一定要將任務(wù)完成)。又比如在清數(shù)據(jù)的時(shí)候,數(shù)據(jù)庫(kù)發(fā)生斷連。這時(shí)候我們會(huì)發(fā)現(xiàn)線程死掉了,任務(wù)終止了,我們需要重啟整個(gè)項(xiàng)目把該定時(shí)任務(wù)起起來(lái)。

解決這些問(wèn)題的關(guān)鍵就是,如何捕獲線程執(zhí)行過(guò)程中產(chǎn)生的異常?

我們查看JDK API我們會(huì)發(fā)現(xiàn)在Thread中有setUncaughtExceptionHandler方法,讓我們可以在線程發(fā)生異常時(shí),調(diào)用該方法。

解決方法:

場(chǎng)景一解決思路:

public class Plan1 {
    
    private SimpleTask task = new SimpleTask();
    
    public static void main(String[] args) {
        Plan1 plan = new Plan1();
        plan.start();
    }
    public void start(){
        Thread thread = new Thread(task);
        //thread.setDaemon(true); //注釋調(diào) 否則看不到輸出
        thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(e.getMessage());
                start();
            }
        });
        thread.start();
    }
    
    class SimpleTask implements Runnable{
        private int task = 10;
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+"--"+"啟動(dòng)");
            while(task>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(System.currentTimeMillis()%3==0){
                    throw new RuntimeException("模擬異常");
                }
                System.out.println(threadName+"--"+"執(zhí)行task"+task);
                task--;
            }
            System.out.println(threadName+"--"+"正常終止");
        }
    }
}

結(jié)果輸出:

Thread-0--啟動(dòng)
Thread-0--執(zhí)行task10
Thread-0--執(zhí)行task9
Thread-0--執(zhí)行task8
Thread-0--執(zhí)行task7
模擬異常
Thread-1--啟動(dòng)
Thread-1--執(zhí)行task6
Thread-1--執(zhí)行task5
模擬異常
Thread-2--啟動(dòng)
Thread-2--執(zhí)行task4
Thread-2--執(zhí)行task3
模擬異常
Thread-3--啟動(dòng)
Thread-3--執(zhí)行task2
模擬異常
Thread-4--啟動(dòng)
Thread-4--執(zhí)行task1
Thread-4--正常終止

還是場(chǎng)景一我們來(lái)看一下線程池的方式,思路是一樣的為什么要再寫一個(gè)單線程的線程池方式呢?

public class Plan3 {
    private SimpleTask task = new SimpleTask();
    private MyFactory factory = new MyFactory(task);
    public static void main(String[] args) {
        Plan3 plan = new Plan3();
        ExecutorService pool = Executors.newSingleThreadExecutor(plan.factory);
        pool.execute(plan.task);
        pool.shutdown();
    }
    
    class MyFactory implements ThreadFactory{
        private SimpleTask task;
        public MyFactory(SimpleTask task) {
            super();
            this.task = task;
        }
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {
                    ExecutorService pool = Executors.newSingleThreadExecutor(new MyFactory(task));
                    pool.execute(task);
                    pool.shutdown();
                }
            });
            return thread;
        }
    }
    
    class SimpleTask implements Runnable{
        private int task = 10;
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+"--"+"啟動(dòng)");
            while(task>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(System.currentTimeMillis()%3==0){
                    throw new RuntimeException("模擬異常");
                }
                System.out.println(threadName+"--"+"執(zhí)行task"+task);
                task--;
            }
            System.out.println(threadName+"--"+"正常終止");
        }
    }
}

結(jié)果輸出:

Thread-0--啟動(dòng)
Thread-0--執(zhí)行task10
Thread-0--執(zhí)行task9
Thread-1--啟動(dòng)
Thread-1--執(zhí)行task8
Thread-2--啟動(dòng)
Thread-2--執(zhí)行task7
Thread-2--執(zhí)行task6
Thread-2--執(zhí)行task5
Thread-2--執(zhí)行task4
Thread-2--執(zhí)行task3
Thread-2--執(zhí)行task2
Thread-3--啟動(dòng)
Thread-3--執(zhí)行task1
Thread-3--正常終止

由于這邊只是用單線程,所以發(fā)現(xiàn)和上面區(qū)別不大。不過(guò)也展示了線程池是如何捕獲線程異常的。

場(chǎng)景二解決方法

現(xiàn)在我們看看場(chǎng)景二定時(shí)任務(wù),為什么我要寫一份單線程池的捕獲異常方式,就是用于和下面做對(duì)比。

定時(shí)任務(wù)我們常常用ScheduledExecutorService,和上述ExecutorService獲取方式一樣。但是如果我們參照上述方式寫定時(shí)任務(wù),然后獲取異常。我們會(huì)發(fā)現(xiàn)我們無(wú)法在uncaughtException方法內(nèi)獲取到線程的異常。異常消失了,或者說(shuō)線程發(fā)生異常根本就沒(méi)調(diào)用uncaughtException方法。后來(lái)查看相關(guān)API,發(fā)現(xiàn)在ScheduledExecutorService獲取異常的方式可以使用ScheduledFuture對(duì)象來(lái)獲取具體方式如下:

public class Plan2 {
    private SimpleTask task = new SimpleTask();
    public static void main(String[] args) {
        Plan2 plan = new Plan2();
        start(plan.task);
    }
    
    public static void start(SimpleTask task){
        ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
        ScheduledFuture<?> future = pool.scheduleAtFixedRate(task, 0, 1000, TimeUnit.MILLISECONDS);
        try {
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            System.out.println(e.getMessage());
            start(task);
        }finally {
            pool.shutdown();
        }
    }
    
    class SimpleTask implements Runnable{
        private volatile int count = 0;
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+"--"+"啟動(dòng)");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(System.currentTimeMillis()%3==0){
                throw new RuntimeException("模擬異常");
            }
            System.out.println(threadName+"--"+"執(zhí)行task"+count);
            count++;
            System.out.println(threadName+"--"+"正常終止");
        }
    }
}

結(jié)果輸出:

pool-1-thread-1--啟動(dòng)
java.lang.RuntimeException: 模擬異常
pool-2-thread-1--啟動(dòng)
pool-2-thread-1--執(zhí)行task0
pool-2-thread-1--正常終止
pool-2-thread-1--啟動(dòng)
pool-2-thread-1--執(zhí)行task1
pool-2-thread-1--正常終止
pool-2-thread-1--啟動(dòng)
pool-2-thread-1--執(zhí)行task2
pool-2-thread-1--正常終止
pool-2-thread-1--啟動(dòng)
java.lang.RuntimeException: 模擬異常
pool-3-thread-1--啟動(dòng)
pool-3-thread-1--執(zhí)行task3
pool-3-thread-1--正常終止
pool-3-thread-1--啟動(dòng)
java.lang.RuntimeException: 模擬異常
pool-4-thread-1--啟動(dòng)
pool-4-thread-1--執(zhí)行task4
pool-4-thread-1--正常終止
.....

至此我們實(shí)現(xiàn)了就算定時(shí)任務(wù)發(fā)生異常,總有一個(gè)線程會(huì)去執(zhí)行。一個(gè)線程倒下,會(huì)有后續(xù)線程補(bǔ)上。

關(guān)于Java線程異常結(jié)束的解決方法就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI