一起Talk Android吧(第三百七十回:多线程之线程池回顾)

Posted talk_8

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起Talk Android吧(第三百七十回:多线程之线程池回顾)相关的知识,希望对你有一定的参考价值。

各位看官们,大家好,上一回中咱们说的是android中多线程之按序操作的例子,这一回中咱们介绍的例子是多线程之线程池回顾。闲话休提,言归正转。让我们一起Talk Android吧!

看官们,我们在在之前的博客中介绍过线程池,如果有忘记的看官可以点击这点参考.

知识回顾

首先我们对线程池做个回顾:JUC中提供Executors类来操作线程池,该类提供了静态方法来创建线程池,比如newFixedThreadPool()/newCachedThreadPool()方法,这些方法返回ExecutorService接口类型的对象,然后使用此对象的execute()/submit()方法把线程添加到线程池中,线程池就会自动管理线程。添加线程的两个方法具有相同的功能,不过submit()方法更加灵活一些,它是重载方法,可以添加Runnable和Callable类型的线程对象到线程池中,相比而言execute()方法只能添加Runnable类型的线程对象到线程池中。最后是关闭线程池操作,不过实际项目中只有发生异常时才会关闭线程池,因为线程需要一直运行,不会停下来。

介绍这么多内容,我们将其总结成线程池使用三步曲:

  1. 创建线程池
  2. 向线程池中添加线程
  3. 关闭线程池

虽然使用Executors类的静态方法创建线程池比较灵活,但是存在一定的的缺陷:引起内存溢出(OOM),本章回中将对这些线程池的知识做扩展,主要是介绍如何创建个性化的线程池。大家可以看到,在程序中使用ExecutorService接口类型的对象表示线程池,JUC中还提供了ExecutorService接口的实现类:ThreadPoolExecutor来表示线程池,创建此类的对象相当于创建线程池。

介绍新内容

创建类的对象需要类的构造方法,因此我们重点看一下ThreadPoolExecutor类的构造方法.它的构造方法是重载方法 ,一共有四个,不同的地方在于参数数量,我们使用参数最多的哪个构造方法来介绍,明白这个构造方法后就可以理解其它的构造方法,下面是该方法的原型的文档:

   /**
     * Creates a new @code ThreadPoolExecutor with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless @code allowCoreThreadTimeOut is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the @code keepAliveTime argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the @code Runnable
     *        tasks submitted by the @code execute method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         @code corePoolSize < 0<br>
     *         @code keepAliveTime < 0<br>
     *         @code maximumPoolSize <= 0<br>
     *         @code maximumPoolSize < corePoolSize
     * @throws NullPointerException if @code workQueue
     *         or @code threadFactory or @code handler is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

文档中已经对构造方法及其的七个参数做了说明,不过我还要做以下的补充,以加深大家的理解:

  • param1:指定线程池中核线程的数量,不能小于0;
  • param2:指定线程池中最多可以容纳的线程数量,它的值大于等于核心线程数量;
  • param3:指定线程池中临时线程的存活时间;
  • param4:指定线程池中临时线程的存活时间单位;
  • param5:指定线程池中的任务队列,线程从任务队列中获取任务;
  • param6:指定线程池中创建线程的工厂方法;
  • param7:指定线程池中拒绝任务的策略;

明白构造方法后,我们介绍线程创建线程逻辑,通过这个逻辑可以更加深入了解这七个参数的含义和用法。大家都知道线程池自动管理线程(创建和销毁),那么线程池什么时候创建线程呢?通常会按照核心线程数量创建核心线程,如果核心线程都在工作而此时有新的工作任务到来时,把任务添加到任务队列中,等待核心线程完成当前工作后会从任务队列中取出任务继续工作,如果核心线程还在忙于当前工作任务而且任务队列也满了(这个要注意),那么创建临时线程,临时线程会从任务队列中取出任务来执行任务。注意核心线程加上临时线程的数量必须小于param2中的数量时才可以,不然创建临时线程时会发生异常。而当所有线程都在忙,而且任务队列也满了时,线程池开始拒绝任务,就会执行param7中的任务策略。

明白这个逻辑后,我们可以结合项目的需要,通过构造方法的参数自定义线程池中线程的数量、任务队列长度等内容。这样创建的线程池比较灵活,因此我们称其为个性化的线程池。回过头来再看看Executors类的静态方法如何创建线程池:

   public static ExecutorService newCachedThreadPool() 
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    

从代码中可以看到这两个静态方法也是是通过ThreadPoolExecutor类的构造方法创建线程池,不过它们默认指定了参数值,从指定的参数中可以看到创建Integer.MAX_VALUE这么大数量的线程池极有可能引起OOM。此外,把核心线程数量和最大线程数量设定为相同的值也不够合理,如果用户给参数赋值Integer.MAX_VALUE,同样会引起OOM。这些就是我们在文章开头时说过的“静态方法有缺陷”。

看官们,关于Android中多线程之按序操作的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!

以上是关于一起Talk Android吧(第三百七十回:多线程之线程池回顾)的主要内容,如果未能解决你的问题,请参考以下文章

一起Talk Android吧(第三百七十六回:如何使用TabLayout)

一起Talk Android吧(第三百七十八回:给ViewPager添加indicator)

一起Talk Android吧(第三百七十三回:多线程版Timer)

一起Talk Android吧(第三百七十五回:如何使用ViewPager2)

一起Talk Android吧(第三百七十一回:多线程之线程池扩展)

一起Talk Android吧(第三百七十四回:多线程之大结局)