Android线程池学习笔记(二)

ThreadPoolExecutor

线程池的实现类。

构造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize——最大核心线程数。核心线程即使空闲也不会被销毁,除非调用allowCoreThreadTimeOut(true)。
  • maximumPoolSize——线程池最多可运行的线程数。该值不小于corePoolSize。
  • keepAliveTime——非核心线程在空闲状态的存活时间。
  • unit——keepAliveTime的时间单位。
  • workQueue——存放等待执行的任务的队列,只有通过execute(Runnable)提交的任务才可能进入该队列。
  • threadFactory——线程池创建通过该工厂创建线程。
  • handler——在线程池满载的情况下,提交的任务交由handler处理。

以上各参数均有响应的setter方法。

ThreadPoolExecutor提供了四种handler:

  1. CallerRunsPolicy——在线程池未关闭的情况,直接在调用execute(Runnable)方法的线程执行任务。
  2. AbortPolicy——抛出一个RejectedExecutionException。
  3. DiscardPolicy——丢弃
  4. DiscardOldestPolicy——丢弃等待队列中最早提交的那个任务,然后重新提交新的任务。

ThreadPoolExecutor默认使用AbortPolicy。

线程池状态和线程数量的指示字段

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctl是一个32位原子整型,高3位表示线程状态(runState),剩余位代码线程数量(workerCount)。所以线程池可容纳的最大线程数是(2^29)-1。
wokerCount未必和存活的线程数一致,比如使用ThreadFactory创建线程失败时,或者线程终结前仍然进行着某些工作时。

线程池状态有以下几种:

  • RUNNING(-1)——可接收新的任务,可执行队列中的任务。
  • SHUTDOWN(0)——不再接收新的任务,仍可执行队列中的任务。
  • STOP(1)——不再接收新的任务,不再执行队列中的任务,中断正在执行的任务。
  • TIDYING(2)——过渡状态。所有任务都已终结,workerCount等于0,terminated()方法执行之前的状态。可以覆写terminated()做一些清理工作。
  • TERMINATED(3)——terminated()执行之后的状态。

状态的更改是递增的,可能的状态改变如下:

  • RUNNING -> SHUTDOWN——调用了shutdown()方法,可能是通过finalize()隐式调用的。
  • (RUNNING or SHUTDOWN) -> STOP——调用了shutdownNow()。
  • SHUTDOWN -> TIDYING——线程池和队列都为空。
  • STOP -> TIDYING——线程池为空。
  • TIDYING -> TERMINATED——terminated()方法执行完毕。

awaitTermination()方法在状态为TERMINATED时返回。

public void execute(Runnable command)

提交任务有几种情况:

  1. 工作线程数 < 核心线程数——创建新的核心线程,并处理该任务。
  2. 工作线程数 >= 核心线程数,队列未满——添加到队列。
  3. 队列满,工作线程数 < 最大线程数——创建非核心线程处理任务。
  4. 交由handler处理。

public boolean prestartCoreThread()

手动启动一个核心线程,这样新来的任务就可以直接运行,从而减少线程启动的时间。如果所有核心线程都已启动,返回false。

public int prestartAllCoreThreads()

手动启动所有核心线程。返回启动的核心线程数。

public void allowCoreThreadTimeOut(boolean value)

设置空闲时,核心线程是否允许超时关闭。存活时间同非核心线程。

public boolean allowsCoreThreadTimeOut()

查询核心线程在空闲时,是否允许超时关闭。

public BlockingQueue<Runnable> getQueue()

获取等待队列。

public boolean remove(Runnable task)

从等待队列中删除task。

public void purge()

将等待队列中所有已经取消的Future任务立即移除。

public int getActiveCount()

返回正在执行任务的线程数。

public int getLargestPoolSize()

返回线程池中出现过的最大的线程数量,不大于最大线程数。

public long getTaskCount()

返回线程池总共执行过的以及正在执行的任务数,该值是个大概值。

public long getCompletedTaskCount()

返回线程池总共执行过的任务数,该值是个大概值。


以上是线程池的基本理解和使用,不想深究的话,到这里也就可以了。

下面是需要注意的点。

关于新建线程

线程池使用ThreadFactory来创建线程,如果没有显示提供ThreadFactory,则使用默认的Executors#DefaultThreadFactory来创建线程:

    private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

从以上代码可以得到如下信息:

  1. 线程名称均是“pool-XX-thread-XX”形式。
  2. 均是非daemon线程。
  3. 优先级均是Thread.NORM_PRIORITY

关于队列选择

常用的队列选择策略有以下几种:

  1. 直接传递——使用SynchronousQueue同步队列实现。该队列不保存任务,可以理解为一个单纯的管道。其“放入”和“取出”都是阻塞的,每个“放入”操作都要等待一个“取出”操作,反之亦然。直接传递,通常要求最大线程数量不受限制,以避免新提交的任务交由handler处理;但这就会导致线程数量不可控。该策略在任务间有内部依赖时,可避免锁住。
  2. 无限队列——使用一个无容量限制的队列,比如LinkedBlockingQueue(FIFO队列)或者PriorityBlockingQueue(可自定义Comparator)。这样在核心线程都在工作时,新的任务会被添加到队列中,最大线程数不会超过核心线程数,也就是说maximumPoolSize这个参数将不起作用。该策略适合任务间完全独立,相互不影响的情况。所谓无限,并非是真的无限,只是容量非常大而已,可能是Integer.MAX_VALUE,也可能是其他值。
  3. 有限队列——比如ArrayBlockingQueue。可以避免资源的过度消耗,但控制起来比较复杂,需要权衡队列容量和最大线程数的关系:使用大队列和小线程数量,会减少CPU的使用,以及操作系统资源的占用,但会降低吞吐率;使用小队列和大线程数,可以充分利用CPU资源,但可能会加大调度开支,同样降低吞吐率。

关于RejectedExecutionHandler

上面介绍了四种默认的RejectedExecutionHandler,同样也可以自己定义。需要注意的是,RejectedExecutionHandler的选择需要参照线程数量和队列选择策略。比如无限队列的情况,可以随意设置。

关于覆写ThreadPoolExecutor

ThreadPoolExecutor提供了3个protected的hook方法。beforeExecute(Thread, Runnable)afterExecute(Runnable, Throwable)分别在每个任务的执行前/后调用,terminated()方法在线程池终结时调用。

以下是一个覆写的例子,添加了pause|resume方法:

class PausableThreadPoolExecutor extends ThreadPoolExecutor {
    private boolean isPaused;
    private ReentrantLock pauseLock = new ReentrantLock();
    private Condition unpaused = pauseLock.newCondition();

    public PausableThreadPoolExecutor(...) { 
        super(...);
     }

    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        pauseLock.lock();
        try {
            while (isPaused) 
                unpaused.await();
        } catch (InterruptedException ie) {
            t.interrupt();
        } finally {
            pauseLock.unlock();
        }
    }

    public void pause() {
        pauseLock.lock();
        try {
            isPaused = true;
        } finally {
            pauseLock.unlock();
        }
    }

    public void resume() {
        pauseLock.lock();
        try {
            isPaused = false;
            unpaused.signalAll();
        } finally {
            pauseLock.unlock();
        }
    }
}

下面学习线程池的实现原理。

线程在哪里

线程池的线程由内部类Worker持有。

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker. */
        public void run() {
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

Worker类继承自AbstractQueuedSynchronizer,该父类这里不深究,只需要知道它是一个锁的实现即可。Worker类还实现了Runnable接口。
由构造方法可知,Worker在创建时会通过ThreadFactory.newThread方法创建一个线程,并将自身作为Runnable对象传递给该线程。
protected修饰的方法是覆写的父类方法,暂且不用管。lock、tryLock、unlock、isLocked是自身锁的调用方法。

Worker的run方法中调用了runWorker方法。如下:

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();//获取Worker类的thread
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {//getTask为阻塞方法
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||//当前线程池状态至少为STOP,不考虑offset,其值为1。则满足的状态为STOP、TIDYING、TERMINATED。
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);//hook方法,可重写
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);//hook方法,可重写
                    }
                } finally {
                    task = null;
                    w.completedTasks++;//已完成的任务数加1
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);//Worker退出时的清理操作
        }
    }

由源码可知,该方法不断从队列中取出任务,交由该Worker所在的线程执行,是执行任务的最终场所。
顺腾摸瓜,这里涉及到getTaskprocessWorkerExit两个方法。

先看processWorkerExit

    /**
     * @param completedAbruptly if the worker died due to user exception
     */
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();//由异常引起的,需要ctl变量中存储的工作线程数量减1。

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);//workers是一个Set,存储了所有活动的Worker。
        } finally {
            mainLock.unlock();
        }

        tryTerminate();//该方法留意一下,下面会介绍

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {//线程池的状态低于STOP,包括RUNNING、SHUTDOWN
            if (!completedAbruptly) {//非异常引起的死亡,比如空闲状态超过了存活时间。
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);//重新添加一个Worker。
        }
    }

该方法为死掉的Worker做一些清理工作。首先将死亡的Worker已经完成的任务数添加到线程池已完成的任务数中,紧接着从线程池的Worker集合中删除该Worker,然后调用tryTerminate,最后根据线程池的状态等,判断是否需要重新添加Worker到Worker集合中。

getTask方法从队列中取出一个任务并返回。在Worker由于一些原因退出时,返回null。这些原因包括:

  1. 线程数量超过maximumPoolSize(比如通过setMaximumPoolSize修改了最大线程数量)。
  2. 线程池被stop。
  3. 线程池被关闭,并且队列为空。
  4. 等待取出任务超时,而超时的Worker需要终结。
    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);//线程池状态

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {//对应情况2、3
            //该条件等价于if (rs >= STOP || (rs >= SHUTDOWN && workQueue.isEmpty()))
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);//当前线程数量

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;//当前Worker是否允许超时关闭

            //对应情况1、4
            if ((wc > maximumPoolSize || (timed && timedOut))//线程数量大于maximumPoolSize或者达到超时条件
                && (wc > 1 || workQueue.isEmpty())) {//并且此时队列为空,或者线程数大于1(在队列非空时,至少需要保留一个Worker来执行任务)
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) ://允许超时关闭则调用poll超时阻塞方法
                    workQueue.take();//否则调用take,一直阻塞下去,直到新任务到来
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

在processWorkerExit方法中,我们接触到了addWorker方法。
该方法根据线程池的状态的最大线程数量,决定是否向线程池添加新的Worker。遇到以下条件之一时,添加失败,返回false:

  1. 线程池已经stop或者达到shutdown的条件。
  2. 当前线程数达到上限。
  3. ThreadFactory创建线程失败。

Worker创建失败时,会回滚一些数据。

    /**
     * @param firstTask 新Worker需要执行的第一个任务,可为null
     * @param core 是否是核心线程
     */
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry://添加一个标签
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))//对应情况1
            //等价于if (rs >= SHUTDOWN && (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty()))
            //之所以有firstTask != null是因为SHUTDOWN后不再接收新任务。
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||//CAPACITY为线程池所能容纳的最大线程数量2^29-1
                    wc >= (core ? corePoolSize : maximumPoolSize))//对应情况2
                    return false;
                if (compareAndIncrementWorkerCount(c))//ctl字段中的Worker数量加1,此时Worker还没有被实际创建,
                //也证实了讲ctl字段时提到的“wokerCount未必和存活的线程数一致”
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)//线程池状态发生变化时,重新执行前面的逻辑
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {//线程创建成功
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {//SHUTDOWN后不再接收新任务
                        if (t.isAlive()) // precheck that t is startable
                            //t.isAlive()返回true,则表明t.start已被调用过,而正常来说,此时还未调用t.start
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)//largestPoolSize是线程池中出现过的最大线程的数量
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);//执行数据回滚
        }
        return workerStarted;
    }

接下来看addWorkerFailed方法。

    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();//讲ctl字段的workerCount减1
            tryTerminate();//查看是否需要终结线程池
        } finally {
            mainLock.unlock();
        }
    }

processWorkerExit和addWorkerFailed都涉及到了tryTerminate方法。
仅tryTerminate方法会将线程池状态置为TIDYING和TERMINATED,且需要满足以下条件之一:

  1. 当前状态为SHUTDOWN,并且线程池和队列都为空。
  2. 当前状态为STOP,并且队列为空。

在执行了可能导致线程池TERMINATED的操作后,必须调用该方法,比如减少了worker的数量,或者shutdown之后从队列中移除了任务。

    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||//线程池正在运行中
                runStateAtLeast(c, TIDYING) ||//线程池正在terminate
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))//或者不满足条件1
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);//workerCount不为0时,中断一个空闲的worker,将中断信号传递下去(如何传递请往下看)
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated();//hook方法,可重写
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));//此处可以看出TIDYING只是一个过渡态。
                        termination.signalAll();//用于唤醒awaitTermination阻塞方法
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

至此我们就见到了可重写的3个hook方法:beforeExecuteafterExecuteterminate

termination.signalAll()这一语句,简单看一下awaitTermination

    public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            while (!runStateAtLeast(ctl.get(), TERMINATED)) {
                if (nanos <= 0L)
                    return false;
                nanos = termination.awaitNanos(nanos);//状态不是TERMINATED,则阻塞下去,等待termination.signalAll()唤醒
            }
            return true;
        } finally {
            mainLock.unlock();
        }
    }

tryTerminate方法中调用了interruptIdleWorkers,仅此处调用传递的onlyOne参数为true:

    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

前面说了,调用interruptIdleWorkers方法是为了将终结信号传递下去,那究竟是如何传递的呢?

  1. interruptIdleWorkers会中断一个Worker。
  2. Worker中断,则runWorker方法就会调用finally块中的processWorkerExit方法,参数completedAbruptly为true。
  3. processWorkerExit方法中会再次调用tryTerminate方法,从而完成终结信号的传递。

至此我们就学习了线程池的实现原理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容