一起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类型的线程对象到线程池中。最后是关闭线程池操作,不过实际项目中只有发生异常时才会关闭线程池,因为线程需要一直运行,不会停下来。
介绍这么多内容,我们将其总结成线程池使用三步曲:
- 创建线程池
- 向线程池中添加线程
- 关闭线程池
虽然使用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)