当线程池中线程出现异常会发生什么

java基础 & 设计模式 专栏收录该内容
67 篇文章 2 订阅

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

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

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

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

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

  • 4
    点赞
  • 0
    评论
  • 5
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值