JDK1.5之后ExecutorService线程池抛RejectedExecutionException的原因剖析以及解决方案

本人亲笔原创, 欢迎转载, 但是转载请注明出处http://blog.csdn.net/izard999/article/details/8552681  谢谢


我们的Android项目前些天把图片下载放到线程池去开线程做了,一般的界面是没有什么问题,但是数据量大一点的ListView有的时候就会抛RejectedExecutionException.

Google了一些资料,其实说的也还可以,就是举了些例子. 大致看了之后, 我还是决定自己追源码!

首先我调用Executors创建的线程池出来的对象是ThreadPoolExecutor,ScheduledThreadPoolExecutor,DelegatedExecutorService这三个类中的一个! 而ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类, DelegatedExecutorService是对ExecutorService进行一层包装.

今天我们这里不讨论每一种线程池是干什么的, 只讨论RejectedExecutionException的原由, 所以我只拿ThreadPoolExecutor这个作为例子说了.

当我们调用Executors.newFixedThreadPool(int nThreads )时会创建一个线程池给我. 源码这个方法的实现是:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
那么我跟进去这个构造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

Ok,当我看到这里的时候, 我已经看到我想要的了.RejectedExecutionHandler这个是拒绝执行者. 那么我看看这个我传进去的defaultHandler是什么, 它就是终止策略

private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();
这个类的实现

public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an <tt>AbortPolicy</tt>.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always.
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException();
        }
    }

直接抛这个异常出去. 那么我明白了, 就是因为我的线程池的拒绝策略是AbortPolicy,所以会导致抛异常!  好, 那么现在我知道为什么抛异常了, 是不是就够了呢? 一般来说用来解决问题是够了, 但是我还想研究下什么情况下会抛这个异常, 和它的终极解决方案!

1:解决方案的探索非常简单,  无非就是RejectedExecutionHandler嘛, 我Ctrl+T看看他有什么实现类就Ok了嘛,  它有四个实现类.AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy. 

关于AbortPolicy我刚才已经说了,它的拒绝策略就是抛异常. 说说下面三个.

CallerRunsPolicy这个的拒绝策略是如果线程池没有shutDown,就会执行需要执行的Runnable

public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a <tt>CallerRunsPolicy</tt>.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

所以, 我们解决抛异常的就直接用CallerRunsPolicy这个策略就可以了.

再来DiscardPolicy, 这个策略就是忽略, 即你随意提交任务, 我不鸟你就完了.

public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a <tt>DiscardPolicy</tt>.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }
DiscardOldestPolicy策略是说忽略最老的任务,然后执行我们提交的.

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a <tt>DiscardOldestPolicy</tt> for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

从实现也可以看出来, 把队列里面顶部的弹掉, 再执行我们的任务

大家可以根据不同所需, 在创建线程池之后, 用你们的ThreadPoolExecutor.setRejectedExecutionHandler(RejectedExecutionHandler handler)去设置你们的拒绝策略


2:那么本文还要继续探索何时这些拒绝策略会被调用呢? 我以ThreadPoolExecutor的execute()方法说事了, ScheduledThreadPoolExecutor的submit方法是大同小异的,请大家自己跟代码去吧.

execute方法的javadoc中有这么一句话:

If the task cannot be submitted for execution, either because this executor has been shutdown or because its capacity has been reached,
the task is handled by the current RejectedExecutionHandler

看看execute的实现先

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
            if (runState == RUNNING && workQueue.offer(command)) {
                if (runState != RUNNING || poolSize == 0)
                    ensureQueuedTaskHandled(command);
            }
            else if (!addIfUnderMaximumPoolSize(command))
                reject(command); // is shutdown or saturated
        }
    }
这里一共涉及到三个方法,addIfUnderCorePoolSize,ensureQueuedTaskHandled,addIfUnderMaximumPoolSize

只要执行到reject(command)这里就会调用我们那个Handler的rejectedExecution()方法.

那三个方法以及javadoc都告诉我们,  如果这个线程池已经shutdown或者容量满了之后, 就会调用拒绝策略.. 请注意,. 从实现来看,  这个容量满了是指当前的线程池以及其缓冲队列的容量都满了 才是满了, 而不是线程池的数量


好了. 下次碰到问题再来读源码吧!   大家有问题可以给我留言.谢谢


猜你喜欢

转载自blog.csdn.net/izard999/article/details/8552681