zoukankan      html  css  js  c++  java
  • 面试官:线程池执行过程中遇到异常会发生什么,怎样处理? Vincent

    • 线程遇到未处理的异常就结束了
    • 线程池中线程频繁出现未捕获异常
    • 问题来了,我们的代码中异常不可能全部捕获
    • 总结

    线程遇到未处理的异常就结束了

    这个好理解,当线程出现未捕获异常的时候就执行不下去了,留给它的就是垃圾回收了。

    线程池中线程频繁出现未捕获异常

    当线程池中线程频繁出现未捕获的异常,那线程的复用率就大大降低了,需要不断地创建新线程。

    做个实验:

    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<V> 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执行结果时抛出

    参考

  • 相关阅读:
    nacos 命名空间
    Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column. To disable safe mode, toggle the option in Preferences
    gitee
    maven引入junit 4.12,项目import org.junit.Test 还是报错.
    gitflow
    202011
    idea 忽略显示不需要的文件
    服务熔断 & 降级区别
    各种微服务框架对比
    zookeeper not connected
  • 原文地址:https://www.cnblogs.com/Vincent-yuan/p/15769074.html
Copyright © 2011-2022 走看看