1. 程式人生 > >JDK1.5之後ExecutorService執行緒池拋RejectedExecutionException的原因剖析以及解決方案

JDK1.5之後ExecutorService執行緒池拋RejectedExecutionException的原因剖析以及解決方案

我們的Android專案前些天把圖片下載放到執行緒池去開執行緒做了,一般的介面是沒有什麼問題,但是資料量大一點的ListView有的時候就會拋RejectedExecutionException.

Google了一些資料,其實說的也還可以,就是舉了些例子. 大致看了之後, 我還是決定自己追原始碼!

首先我呼叫Executors建立的執行緒池出來的物件是ThreadPoolExecutor,ScheduledThreadPoolExecutor,DelegatedExecutorService這三個類中的一個! 而ScheduledThreadPoolExecutor是ThreadPoolExecutor的子類, DelegatedExecutorService是對ExecutorService進行一層包裝.

今天我們這裡不討論每一種執行緒池是幹什麼的, 只討論RejectedExecutionException的原由, 所以我只拿ThreadPoolExecutor這個作為例子說了.

當我們呼叫Executors.newFixedThreadPool(intnThreads )時會建立一個執行緒池給我. 原始碼這個方法的實現是:

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或者容量滿了之後, 就會呼叫拒絕策略.. 請注意,. 從實現來看, 這個容量滿了是指當前的執行緒池以及其緩衝佇列的容量都滿了 才是滿了, 而不是執行緒池的數量

好了. 下次碰到問題再來讀原始碼吧! 大家有問題可以給我留言.謝謝