面试官(线程池执行过程中遇到异常会发生什么,怎样处理())

线程遇到未处理的异常就结束了 这个好理解,当线程出现未捕获异常的时候就执行不下去了,留给它的就是垃圾回收了。
线程池中线程频繁出现未捕获异常 当线程池中线程频繁出现未捕获的异常,那线程的复用率就大大降低了,需要不断地创建新线程。
做个实验:

public class ThreadExecutor { private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200), new ThreadFactoryBuilder().setNameFormat("customThread %d").build()); @Test public void test() { IntStream.rangeClosed(1, 5).forEach(i -> { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } threadPoolExecutor.execute(() -> { int j = 1/0; }); }); } }

新建一个只有一个线程的线程池,每隔0.1s提交一个任务,任务中是一个1/0的计算。
Exception in thread "customThread 0" java.lang.ArithmeticException: / by zero at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:748) Exception in thread "customThread 1" java.lang.ArithmeticException: / by zero at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:748) Exception in thread "customThread 2" java.lang.ArithmeticException: / by zero at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:748) Exception in thread "customThread 3" java.lang.ArithmeticException: / by zero at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:748) Exception in thread "customThread 4" java.lang.ArithmeticException: / by zero at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:748) Exception in thread "customThread 5" java.lang.ArithmeticException: / by zero at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:748)

可见每次执行的线程都不一样,之前的线程都没有复用。原因是因为出现了未捕获的异常。
我们把异常捕获试试:
public class ThreadExecutor { private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200), new ThreadFactoryBuilder().setNameFormat("customThread %d").build()); @Test public void test() { IntStream.rangeClosed(1, 5).forEach(i -> { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } threadPoolExecutor.execute(() -> { try { int j = 1 / 0; } catch (Exception e) { System.out.println(Thread.currentThread().getName() +" "+ e.getMessage()); } }); }); } }

customThread 0 / by zero customThread 0 / by zero customThread 0 / by zero customThread 0 / by zero customThread 0 / by zero

可见当异常捕获了,线程就可以复用了。
问题来了,我们的代码中异常不可能全部捕获 如果要捕获那些没被业务代码捕获的异常,可以设置Thread类的uncaughtExceptionHandler属性。这时使用ThreadFactoryBuilder会比较方便,ThreadFactoryBuilder是guava提供的ThreadFactory生成器。
new ThreadFactoryBuilder() .setNameFormat("customThread %d") .setUncaughtExceptionHandler((t, e) -> System.out.println(t.getName() + "发生异常" + e.getCause())) .build()

修改之后:
public class ThreadExecutor { private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200), new ThreadFactoryBuilder() .setNameFormat("customThread %d") .setUncaughtExceptionHandler((t, e) -> System.out.println("UncaughtExceptionHandler捕获到:" + t.getName() + "发生异常" + e.getMessage())) .build()); @Test public void test() { IntStream.rangeClosed(1, 5).forEach(i -> { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }threadPoolExecutor.execute(() -> { System.out.println("线程" + Thread.currentThread().getName() + "执行"); int j = 1 / 0; }); }); } }

线程customThread 0执行 UncaughtExceptionHandler捕获到:customThread 0发生异常/ by zero 线程customThread 1执行 UncaughtExceptionHandler捕获到:customThread 1发生异常/ by zero 线程customThread 2执行 UncaughtExceptionHandler捕获到:customThread 2发生异常/ by zero 线程customThread 3执行 UncaughtExceptionHandler捕获到:customThread 3发生异常/ by zero 线程customThread 4执行 UncaughtExceptionHandler捕获到:customThread 4发生异常/ by zero

可见,结果并不是我们想象的那样,线程池中原有的线程没有复用!所以通过UncaughtExceptionHandler想将异常吞掉使线程复用这招貌似行不通。它只是做了一层异常的保底处理。
将excute改成submit试试
public class ThreadExecutor { private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200), new ThreadFactoryBuilder() .setNameFormat("customThread %d") .setUncaughtExceptionHandler((t, e) -> System.out.println("UncaughtExceptionHandler捕获到:" + t.getName() + "发生异常" + e.getMessage())) .build()); @Test public void test() { IntStream.rangeClosed(1, 5).forEach(i -> { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }Future future = threadPoolExecutor.submit(() -> { System.out.println("线程" + Thread.currentThread().getName() + "执行"); int j = 1 / 0; }); try { future.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }); } }

线程customThread 0执行 java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero 线程customThread 0执行 java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero 线程customThread 0执行 java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero 线程customThread 0执行 java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero 线程customThread 0执行 java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero

通过submit提交线程可以屏蔽线程中产生的异常,达到线程复用。当get()执行结果时异常才会抛出。
原因是通过submit提交的线程,当发生异常时,会将异常保存,待future.get(); 时才会抛出。
这是Futuretask的部分run()方法,看setException:
public void run() { try { Callable c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } if (ran) set(result); } } }protected void setException(Throwable t) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome = t; UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state finishCompletion(); } }

将异常存在outcome对象中,没有抛出,再看get方法:
public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); return report(s); } private V report(int s) throws ExecutionException { Object x = outcome; if (s == NORMAL) return (V)x; if (s >= CANCELLED) throw new CancellationException(); throw new ExecutionException((Throwable)x); }

当outcome是异常时才抛出。
总结 1、线程池中线程中异常尽量手动捕获
2、通过设置ThreadFactory的UncaughtExceptionHandler可以对未捕获的异常做保底处理,通过execute提交任务,线程依然会中断,而通过submit提交任务,可以获取线程执行结果,线程异常会在get执行结果时抛出。
本文链接:https://blog.csdn.net/weixin_...
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
近期热文推荐:
1.1,000+ 道 Java面试题及答案整理(2021最新版)
2.别在再满屏的 if/ else 了,试试策略模式,真香!!
3.卧槽!Java 中的 xx ≠ null 是什么新语法?
4.Spring Boot 2.5 重磅发布,黑暗模式太炸了!
5.《Java开发手册(嵩山版)》最新发布,速速下载!
【面试官(线程池执行过程中遇到异常会发生什么,怎样处理())】觉得不错,别忘了随手点赞+转发哦!

    推荐阅读