ThreadPoolExecutor(七)——总结&补充

1.关于异常

线程池在执行execute方法和submit任务的时候,如果任务中抛出了异常,外部线程无法感知,所以要做一些措施来进行处理,下面就说一下产生这种效果的原因以及一些可用的处理方法。
关于异常的处理内容参考了文章并进行了相关补充: ExecutorService-10个要诀和技巧

1.execute执行的任务抛出异常

1.原理

测试代码:

public static void main(String[] args) {
        Executors.newFixedThreadPool(1);
        try {
            Executors.newFixedThreadPool(1).execute(new Runnable() {
                @Override public void run() {
                    int i=1/0;
                }
            });
        } catch (Exception e) {
            System.out.println("catch!!!!");
        } finally {
            System.out.println("normal");
        }
    }
这个异常会被内部吞掉的,不会被catch块catch到。
但是程序会用System.err和e.printStackTrace方式打印异常栈信息,是由下面这个方法触发的:

/**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
看注释,这个方法是用来分发处理一个uncaught的异常交由UncaughtExceptionHandler来处理,是由JVM底层来调用的。

具体的实现在ThreadGroup类中,有个uncaughtException方法,

/**
     * Called by the Java Virtual Machine when a thread in this
     * thread group stops because of an uncaught exception, and the thread
     * does not have a specific {@link Thread.UncaughtExceptionHandler}
     * installed.
     * <p>
     * Applications can override this method in subclasses of
     * <code>ThreadGroup</code> to provide alternative handling of
     * uncaught exceptions.
     *
     * @param   t   the thread that is about to exit.
     * @param   e   the uncaught exception.
     * @since   JDK1.0
     */
    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }
先看注释,该方法是由JVM调用,当一个线程由于一个uncaught异常而导致线程终止,而且该线程没有指定一个UncaughtExceptionHandler。

在else if块中会用System.err和e.printStackTrace配合打印异常信息。

这种情况为了保证我们的程序在运行时能够感知异常,我们可以在我们的task的run方法中用try catch把语句包起来,然后在catch记录日志和监控。注意这里既是把线程包装一下重新跑出去也是没用的。

2.解决方案
自己把线程封装一下
/**
     * 第一种实现自己定义线程,同时自己定义一个ThreadFactory的实现呢
     */
    static class MyAppThread extends Thread {
        public static final String DEFAULT_NAME = "MyAppThread";
        private static final AtomicInteger created = new AtomicInteger();
        private static final AtomicInteger alive = new AtomicInteger();

        public MyAppThread(Runnable r) {
            this(r, DEFAULT_NAME);
        }

        public MyAppThread(Runnable runnable, String name) {
            super(runnable, name + "-" + created.incrementAndGet());
            this.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                public void uncaughtException(Thread t,
                        Throwable e) {
                    logger.error("UNCAUGHT1 in thread " + t.getName(), e);
                }
            });
        }

        public void run() {
            System.out.println("Created " + getName());
            try {
                alive.incrementAndGet();
                super.run();
            } finally {
                alive.decrementAndGet();
                System.out.println("Exiting " + getName());
            }
        }

        public static int getThreadsCreated() {
            return created.get();
        }

        public static int getThreadsAlive() {
            return alive.get();
        }
    }
自己封装线程的工厂类
public class MyThreadFactory2 {
        public Thread newThread(Runnable runnable) {
            Thread ret = Executors.defaultThreadFactory().newThread(runnable);
            ret.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                public void uncaughtException(Thread t,
                        Throwable e) {
                    logger.error("UNCAUGHT2 in thread " + t.getName(), e);
                }
            });
            return ret;
        }
    }
在构造线程池的时候,作为参数传递我们自己封装的线程工厂。

2.submit方法提交的任务中抛出异常

/**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
线程池的submit方法提交的任务是一个FutureTask,它既含有Runnable的任务属性也含有Future的属性。FutureTask的run方法中调用的是内部实现的AbstractQueuedSynchronizer的innerRun方法,

void innerRun() {
            if (!compareAndSetState(READY, RUNNING))
                return;

            runner = Thread.currentThread();
            if (getState() == RUNNING) { // recheck after setting thread
                V result;
                try {
                    result = callable.call();
                } catch (Throwable ex) {
                    setException(ex);
                    return;
                }
                set(result);
            } else {
                releaseShared(0); // cancel
            }
        }
所以call方法抛出异常的话,会被catch捕获并用setException进行异常的设置。在设置异常之后,用future的get(内部是Sync的innerGet方法)方法获取结果的时候会抛出异常,

V innerGet() throws InterruptedException, ExecutionException {
            acquireSharedInterruptibly(0);
            if (getState() == CANCELLED)
                throw new CancellationException();
            if (exception != null)
                throw new ExecutionException(exception);
            return result;
        }
所以为了确保外部感知到这个异常,有两个方法:

1.一定要是用future的阻塞的get方法来获取结果。如果调用带超时时间的get方法,如果异常在超时之后发生的话,外部也无法感知到这个异常。如果业务上在超时之后已经不关心这个异常了的话,也可以只对超时进行监控和日志输出。

2.对call方法内部进行try catch包裹,在有异常发生的时候,设置一个特殊值来表示异常发生情况下的返回,同时记录日志和监控。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值