吊打面試官之ThreadPoolExecutor深入理解

ThreadPoolExecutor
提問?
1,爲什麼不能使用JDK自帶的Executors去創建線程池。 (阿里手冊中也有這個限制)?
比如:ExecutorService executorService = Executors.newCachedThreadPool();
查看源碼:

return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                              60L, TimeUnit.SECONDS,
                              new SynchronousQueue<Runnable>());

Integer.MAX_VALUE:這個會造成非常大的問題,很容易OOM。

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

Integer.MAX_VALUE:這個會造成非常大的問題,很容易OOM。

 ExecutorService executorService2 = Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

LinkedBlockingQueue(),默認也是Integer.MAX_VALUE長度,對列容易堆積到很大
的時候就會造成OOM。

ExecutorService executorService1 = Executors.newFixedThreadPool(1);
源碼查看:
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

LinkedBlockingQueue(),默認也是Integer.MAX_VALUE長度,對列容易堆積到很大
的時候就會造成OOM。

如果一個線程池的線程異常了,如何處理這個異常?
1,必須要求不能影響其他線程工作。

public class ThreadPoolExecutorExceptionProcess {

    public static void main(String[] args) {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("DeviceStatusThreadPoolExecutor Thread").build();
        int queueCapacity = 2, corePoolSize = 2, maximumPoolSize = 2;
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(queueCapacity);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 10,
                TimeUnit.SECONDS, arrayBlockingQueue);

        threadPoolExecutor.execute(new MyThread(2));
        threadPoolExecutor.submit(new MyThread(2));
    }

    static class MyThread extends Thread {
        private int i;

        public MyThread(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            System.out.println(i+"執行了。。。。。。");
            throw new RuntimeException("run exception");
        }
    }

}

這個代碼你們心裏清楚如何處理嗎?

threadPoolExecutor.execute(new MyThread(2));
threadPoolExecutor.submit(new MyThread(2));

那個會拋出異常? execute還是submit。
執行execute方法
在這裏插入圖片描述
執行submit方法
在這裏插入圖片描述
異常吃了。
我們看看這個兩個方法的源碼

public void execute(Runnable command) 
public Future<?> submit(Runnable task)

Submit方法有一個 Future對象

執行execute斷點debug
在這裏插入圖片描述
我們看到執行到了 afterExexute(task,thrown);這個方法。這個方法可以擴展你需要捕獲的異常處理,繼續debug
在這裏插入圖片描述
這個時候會調用uncaughtException這個方法
在這裏插入圖片描述
這個是一個接口的方法,ThreadGroup實現了其接口,繼續debug
在這裏插入圖片描述
可以看到來了到這裏。最終打印出來異常棧數據和信息。 UncaughtExceptionHandler解釋一下這個接口。
在這裏插入圖片描述

就是一個線程因未捕獲異常而即將終止的時候,JVM蔣使用Thread.getUncaughtExceptionHandler()獲取已經設置的UncaughtExceptionHandler實例並且通過調用uncaughtException方法而傳遞相關的異常信息,如果一個線程沒有明確的制定UncaughtExceptionHandler,就會讓ThreadGroup對象作爲他的handler,如果ThreadGroup對象的異常沒有特殊要求,ThreadGroup就調用轉發給默認的異常處理器就是上面截圖看到的處理。

單個線程處理異常的方式
//單線程線程異常處理方式
Thread thread=new Thread();
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        //處理異常
    }
});

異常處理的常見方式
1,直接在我們的業務方法中直接try-catch捕獲所有的異常,直接在catch塊中進行異常處理。
2,線程池方式處理 繼承ThreadPoolExecutor實現afterExecute方法的實現就可以了

public class ThreadPoolExecutorExtend extends ThreadPoolExecutor {
    public ThreadPoolExecutorExtend(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        //錯誤自定義處理
        System.out.println("自定義錯誤處理");
    }

}

2,設置UncaughtExceptionHandler

 public class ThreadPoolExecutorExceptionProcess {

    public static void main(String[] args) {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("DeviceStatusThreadPoolExecutor Thread").build();
        int queueCapacity = 2, corePoolSize = 2, maximumPoolSize = 2;
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(queueCapacity);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutorExtend(corePoolSize, maximumPoolSize, 10,
                TimeUnit.SECONDS, arrayBlockingQueue, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
                        //自定義錯誤
                    }
                });
                return thread;
            }
        });
        //單獨實例線程異常處理方式
        MyThread thread=new MyThread(2);
        threadPoolExecutor.execute(thread);

    }

    static class MyThread extends Thread {
        private int i;

        public MyThread(int i) {
            this.i = i;
//            this.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
//                @Override
//                public void uncaughtException(Thread t, Throwable e) {
//                    System.out.println("是我自己處理的異常"+t.getName()+e.getMessage());
//                }
//            });
        }

        @Override
        public void run() {
            System.out.println(i+"執行了。。。。。。");
            throw new RuntimeException("run exception");
        }
    }

}

3,自定義ThreadGroup進行錯誤處理

public class ThreadPoolExecutorExceptionProcess {

    static class ThreadGroupExtend extends  ThreadGroup{

        public ThreadGroupExtend(String name) {
            super(name);
        }

        public ThreadGroupExtend(ThreadGroup parent, String name) {
            super(parent, name);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            //異常業務處理
            System.out.println("業務異常處理。。。。");
        }

    }

    public static void main(String[] args) {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("DeviceStatusThreadPoolExecutor Thread").build();
        int queueCapacity = 2, corePoolSize = 2, maximumPoolSize = 2;
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(queueCapacity);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutorExtend(corePoolSize, maximumPoolSize, 10,
                TimeUnit.SECONDS, arrayBlockingQueue, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(new ThreadGroupExtend("CustomerThreadGroup"),r);
            }
        });
        //單獨實例線程異常處理方式
        MyThread thread=new MyThread(2);
        threadPoolExecutor.execute(thread);

    }

    static class MyThread extends Thread {
        private int i;

        public MyThread(int i) {
            this.i = i;
//            this.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
//                @Override
//                public void uncaughtException(Thread t, Throwable e) {
//                    System.out.println("是我自己處理的異常"+t.getName()+e.getMessage());
//                }
//            });
        }

        @Override
        public void run() {
            System.out.println(i+"執行了。。。。。。");
            throw new RuntimeException("run exception");
        }
    }



}
submit方法錯誤處理方式
Future<?> submit = threadPoolExecutor.submit(thread);
try {
    submit.get();
} catch (Exception e) {
    //錯誤處理
}

源碼分析
執行submit方法—> 同樣會調用threadPoolExecutor的execute(ftask)

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

繼續調用threadPoolExecutor的 runWorker(Worker w)
在這裏插入圖片描述
可以看到已經被封裝成了FutureTask了,我們進入到FutureTask中去看看run方法 debug
在這裏插入圖片描述
看看異常,緊接着我們進入到setException裏面去看看
在這裏插入圖片描述
在這裏插入圖片描述
異常數據被賦值到這對象上。
然後在調用 get方法
在這裏插入圖片描述
在調用 report方法
在這裏插入圖片描述
這個時候x就被賦值爲異常對象數據。返回

總結

1,java線程池會捕獲任務拋出的異常和錯誤,處理策略會受到我們提交任務的方式而不同。

2,submit()方式提交的任務會返給我們一個Future,如果業務方法有拋出異常,當我們調用java.util.concurrent.Future#get()方法時會拋出包裝後的java.util.concurrent.ExecutionException。自己處理相應的錯誤
3,execute()方式提交的任務,java處理的默認策略是使用System.err.print("Exception in thread “” + t.getName() + “” ")輸出日誌,但是該日誌不會打印到我們的日誌文件中,只會在catalina.out文件中。
4,可以修改java線程池的默認處理策略,具體修改方式見上面如何處理線程異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章