每个 Android 应用进程在创建时,会同时创建一个线程,我们称之为主线程,负责更新 UI 界面以及和处理用户之间的交互,因此,在 Android 中,我们又称之为 UI 线程。一个进程中 UI 线程只有一个,为了不造成界面卡顿、提高用户体验,我们势必要将一些耗时操作交由子线程来执行。

使用子线程的方式主要分两种:

  • 直接使用 ThreadRunnable 等创建子并使用线程

  • 使用线程池创建并使用子线程

线程池是什么

线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新创建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求;每个线程都有被分配一个任务,一旦任务完成了,线程回到池子中并等待下一次分配任务。

一般情况下,推荐使用线程池来创建和使用子线程,不建议使用第一种方式。

为何要用线程池

上面说了,在 Android 开发过程中,建议使用线程池来创建和使用子线程,那么使用线程池的好处有哪些呢?

  • 线程池改进了一个应用程序的响应时间。由于线程池中的线程已经准备好且等待被分配任务,应用程序可以直接拿来使用而不用新建一个线程。

  • 线程池节省了 CLR 为每个短生存周期任务创建一个完整的线程开销并可以在任务完成后回收资源。

  • 线程池根据当前在系统中运行的进程来优化线程片。

  • 线程池允许我们开启多个任务而不用为每个线程设置属性。

  • 线程池允许我们为正在执行的任务的程序参数传递一个包含状态信息的对象引用。

  • 线程池可以用来解决处理一个特定请求最大线程数量限制问题。

  • 系统分配给每个应用的线程栈是固定的,使用线程池可以有效地避免线程栈溢出引起的应用崩溃。

Android 中的线程池

Android 中线程池的真正实现是 ThreadPoolExecutor,其构造方法有 5 个,通过一系列参数来配置线程池。下面介绍一个比较常用的构造方法及其参数的含义。

  1. public ThreadPoolExecutor(int corePoolSize,
  2. int maximumPoolSize,
  3. long keepAliveTime,
  4. TimeUnit unit,
  5. BlockingQueue<Runnable> workQueue,
  6. ThreadFactory threadFactory)
 
参数 含义
corePoolSize int: 线程池的核心线程数,默认情况下,核心线程回一直在线程池中存活,即使他们处于闲置状态。如果将 allowCoreThreadTimeOut 的属性设置为 true,那么闲置的核心线程在等待新任务到来时会有超时策略,这个时间间隔由 keepAliveTime 所指定,当等待时间超过 keepAliveTime 所指定的时长后,核心线程就会被终止。
maximumPoolSize int: 线程池中允许的线程最大数量,当活动线程达到这个数值后,后续的新任务会被阻塞。int: 线程池中允许的线程最大数量,当活动线程达到这个数值后,后续的新任务会被阻塞。
keepAliveTime long: 非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当 allowCoreThreadTimeOut 属性被设置为 true 时,该参数同样会作用于核心线程。
unit TimeUnit: keepAliveTime 参数的时间单位 ,参数为 TimeUnit 的枚举,常见的有 TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECOND(秒) 等。
workQueue BlockingQueue: 线程池中的任务队列,通过线程池的 execute 方法提交的 Runnable 对象会存储在这个参数中。
threadFactory ThreadFactory: 创建线程的线程工厂。ThreadFactory 是一个接口,只有一个方法:Thread newThread (Runnable r)
 
Throws  
IllegalArgumentException

符合以下任一条件,则抛出此异常:

corePoolSize < 0

keepAliveTime < 0

maximumPoolSize <= 0

maximumPoolSize < corePoolSize

NullPointerException workQueue 或者 threadFactory 为 null 时,抛出此异常。

ThreadPoolExecutor 执行任务时大致遵循如下规则:

  1. 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。

  2. 如果线程池中的线程数量已经达到或超过核心线程的数量,那么任务会被插入到任务队列中等待执行。

  3. 如果步骤 2 中无法将任务插入到任务队列,则表示任务队列已满。此时,如果线程数量未达到 maximumPoolSize 值,则会立即启动一个非核心线程来执行任务。

  4. 如果步骤 3 中线程数量大于或等于 maximumPoolSize 值,则拒绝执行次任务,此时 ThreadPoolExecutor 会调用 RejectedExecutionHandlerrejectedExecution 来通知调用者。

RejectedExecutionHandler 是线程池持有的一个对象,用于不能由 ThreadPoolExecutor 执行的任务的处理

Android 中线程池的类型及区别

Android 中最常见线程池有四种,分别是:FixedThreadPoolCacheThreadPoolScheduledThreadPool 以及 SingleThreadPool,它们都直接或间接的通过配置 ThreadPoolExecutor 来实现自己的功能特性。

1. FixedThreadPool

线程数量固定的线程池,通过 ExecutorsnewFixedThreadPool 方法创建。有两个重载的创建方法:

  1. public static ExecutorService newFixedThreadPool(int nThreads) {
  2. return new ThreadPoolExecutor(nThreads, nThreads,
  3. 0L, TimeUnit.MILLISECONDS,
  4. new LinkedBlockingQueue<Runnable>());
  5. }
  1. public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
  2. return new ThreadPoolExecutor(nThreads, nThreads,
  3. 0L, TimeUnit.MILLISECONDS,
  4. new LinkedBlockingQueue<Runnable>(),
  5. threadFactory);
  6. }

除非线程池被关闭,否则线程不会被回收,即时线程处于空闲状态。如果在所有线程都处于活动状态时提交额外的任务,它们将在队列中等待,直到有一个线程可用为止。由创建方法可知,FixedThreadPool 只有核心线程并且这个核心线程没有超时机制(keepAliveTime 参数为 0L),加上线程不会被回收,因此使用此类线程池可以快速地响应外界的请求。

2. CacheThreadPool

线程数量不定的线程池,通过 ExecutorsnewCachedThreadPool 方法创建。有两个重载的创建方法:

  1. public static ExecutorService newCachedThreadPool() {
  2. return new ThreadPoolExecutor(/* corePoolSize*/ 0, Integer.MAX_VALUE,
  3. 60L, TimeUnit.SECONDS,
  4. new SynchronousQueue<Runnable>());
  5. }
  6. public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
  7. return new ThreadPoolExecutor(/* corePoolSize*/ 0, Integer.MAX_VALUE,
  8. 60L, TimeUnit.SECONDS,
  9. new SynchronousQueue<Runnable>(),
  10. threadFactory);
  11. }

CacheThreadPool 有以下个特征:

  • 没有核心线程( corePoolSize 参数为 0 ),只有非核心线程且非核心线程的数量为 Integer.MAX_VALUE,这就相当于非核心线程的数量可以无限大。

  • 线程池的线程处于空闲状态时,线程池会重用空闲的线程来处理新任务,否则创建新的线程来处理,新创建的线程会添加到线程池中。这将提高执行许多短期异步任务的程序性能。

  • 闲置时间超过 60 秒的空闲线程会被回收(keepAliveTime 参数为 60L )。因此,闲置时间足够长的 CacheThreadPool 也不会消耗任何系统资源。

3. ScheduledThreadPool

核心线程数量固定,非核心线程数量不定的线程池,通过 Executorsnewscheduledthreadpool 方法创建。有两个重载的创建方法:

  1. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
  2. return new ScheduledThreadPoolExecutor(corePoolSize);
  3. }
  4.  
  5. public static ScheduledExecutorService newScheduledThreadPool(
  6. int corePoolSize, ThreadFactory threadFactory) {
  7. return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
  8. }

ScheduledThreadPool 相比较其他三种线程池,有特殊性,由 ScheduledThreadPoolExecutor 实现, newscheduledthreadpool 方法也是通过创建 ScheduledThreadPoolExecutor 的实例来完成线程池的创建,代码如下:

  1. public ScheduledThreadPoolExecutor(int corePoolSize) {
  2. super(corePoolSize, Integer.MAX_VALUE,
  3. DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
  4. new DelayedWorkQueue());
  5. }
  6.  
  7. public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) { super(corePoolSize, Integer.MAX_VALUE,
  8. DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
  9. new DelayedWorkQueue(), threadFactory);
  10. }

ScheduledThreadPoolExecutor 的构造函数可知, ScheduledThreadPool 的核心线程数量是固定的,由传入的 corePoolSize 参数决定,非核心线程数量可以无限大。非核心线程闲置回收的超时时间为 10秒DEFAULT_KEEPALIVE_MILLIS 的值为 10L )。这类线程主要用于执行定时任务或者具有周期性的重复任务。

4. SingleThreadPool

只有一个核心线程,通过 Executorsnewsinglethreadexecutor 方法创建。有两个重载的创建方法:

  1. public static ExecutorService newSingleThreadExecutor() {
  2. return new FinalizableDelegatedExecutorService
  3. (new ThreadPoolExecutor(/* corePoolSize*/ 1, /* maximumPoolSize*/ 1,
  4. /* keepAliveTime*/ 0L, TimeUnit.MILLISECONDS,
  5. new LinkedBlockingQueue<Runnable>()));
  6. }
  7.  
  8. public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
  9. return new FinalizableDelegatedExecutorService
  10. (new ThreadPoolExecutor(/* corePoolSize*/ 1, /* maximumPoolSize*/ 1,
  11. /* keepAliveTime*/ 0L, TimeUnit.MILLISECONDS,
  12. new LinkedBlockingQueue<Runnable>(),
  13. threadFactory));
  14. }

SingleThreadPool 由 代理类 FinalizableDelegatedExecutorService 创建。该线程池只有一个线程(核心线程),并且该线程池的任务队列是无上限的,这就确保了所有的任务都在同一个线程中顺序执行。

注意,如果由于在执行期间出现故障而导致该线程终止,那么如果需要执行后续任务,则新线程将取而代之。

四类线程池的区别

上面分别对 Android 中常见的 4 种线程池进行了简单的介绍,除了这 4 种系统提供的线程池外,我们在使用的过程中,也可以根据需要直接通过 ThreadPoolExecutor 的构造函数来灵活的配置线程池。那么,上述的 4 种线程池,其区别在哪呢?了解其区别有助于我们去选择更为合适的线程池或者直接通过 ThreadPoolExecutor 来配置更灵活的线程池。

FixedThreadPool 线程固定,且不会被回收,能够更快的响应外界请求。

CachedThreadPool 只有非核心线程,且线程数相当于无限大,任何任务都会被立即执行。比较适合执行大量的耗时较少的任务。

ScheduledThreadPool 主要用于执行定时任务或者具有周期性的重复任务。

SingleThreadPool 只有一个核心线程,确保所有任务都在同一线程中按顺序完成。因此不需要处理线程同步的问题。

参考

Android 线程池的类型、区别以及为何要用线程池的更多相关文章

  1. 【多线程 5】线程池的类型以及submit()和execute()的区别

    就跟题目说的一样,本篇博客,本宝宝主要介绍两个方面的内容,其一:线程池的类型及其应用场景:其二:submit和execute的区别.那么需要再次重申的是,对于概念性的东西,我一般都是从网上挑选截取,再 ...

  2. 线程池的类型以及执行线程submit()和execute()的区别

    就跟题目说的一样,本篇博客,本宝宝主要介绍两个方面的内容,其一:线程池的类型及其应用场景:其二:submit和execute的区别.那么需要再次重申的是,对于概念性的东西,我一般都是从网上挑选截取,再 ...

  3. Java线程池种类、区别和适用场景

    newCachedThreadPool: 底层:返回ThreadPoolExecutor实例,corePoolSize为0:maximumPoolSize为Integer.MAX_VALUE:keep ...

  4. Java线程状态、线程start方法源码、多线程、Java线程池、如何停止一个线程

    下面将依次介绍: 1. 线程状态.Java线程状态和线程池状态 2. start方法源码 3. 什么是线程池? 4. 线程池的工作原理和使用线程池的好处 5. ThreadPoolExecutor中的 ...

  5. 第11章 Windows线程池(1)_传统的Windows线程池

    第11章 Windows线程池 11.1 传统的Windows线程池及API (1)线程池中的几种底层线程 ①可变数量的长任务线程:WT_EXECUTELONGFUNCTION ②Timer线程:调用 ...

  6. Java:多线程,线程池,用Executors静态工厂生成常用线程池

    一: newSingleThreadExecutor 创建一个单线程的线程池,以无界队列方式运行.这个线程池只有一个线程在工作(如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它.)此线程池 ...

  7. 线程、进程的区别,Java的几个线程状态

    线程.进程的区别        进程的定义:进程就是程序在一个数据集合上的一次执行过程.他与程序的区别在于程序是静态的代码,而进程是动态的执行过程. 进程的特性:1.结构性,进程由程序块.数据块.进程 ...

  8. 原生线程池这么强大,Tomcat 为何还需扩展线程池?

    前言 Tomcat/Jetty 是目前比较流行的 Web 容器,两者接受请求之后都会转交给线程池处理,这样可以有效提高处理的能力与并发度.JDK 提高完整线程池实现,但是 Tomcat/Jetty 都 ...

  9. Android(四)-JVM与DVM区别

    JVM与DVM区别 1.由来 Android是基于java的既然java已经有了java虚拟机,为什么android还要弄一个DVM了?最重要的就是版权问题,一开始就是用的 JVM,没过多久就被SUN ...

随机推荐

  1. HBase 使用外部的 zookeeper

    HBase 启动时,默认会根据hbase-site.xml文件中的如下设置端口上启动一个zookeeper服务: <property> <name>hbase.zookeepe ...

  2. suse-Linux下安装Oracle11g服务器

    系统要求 Linux安装Oracle系统要求 系统要求 说明 内存 必须高于1G的物理内存 交换空间 一般为内存的2倍,例如:1G的内存可以设置swap 分区为3G大小 硬盘 5G以上 2.修改操作系 ...

  3. IDEA里如何多种方式打jar包,然后上传到集群

    关于IDEA里如何多种方式打jar包,然后上传到集群的问题? 前期准备,就是在,IDEA里,maven来创建项目.这里不多赘述. 1)用maven项目来打包,我推荐这个. (强烈推荐,简单又快速) S ...

  4. Debugging Ruby in VS Code

    原文  https://dev.to/dnamsons/ruby-debugging-in-vscode-3bkj https://github.com/Microsoft/vscode-recipe ...

  5. MYSQL的NOW和SYSDATE函数的区别

    在MySQL Performance Blog博客上看到一篇文章介绍now()和sysdate()函数. 想起很多朋友专门问在MySQL里面提供now()和sysdate()函数,都是表示取得当前时间 ...

  6. Eclipse: User Operation is waiting for “Building Workspace”

    这个情况可能有多个原因导致,比如,非正常关闭eclipse,时钟不匹配等等,可能解决的方法有: 1. 删除<workspace_folder>/.metadata/.lock文件 2. e ...

  7. STIL文件(DFT/IC测试方向)

    相信很多工程师,特别是刚入行或准备入行的兄弟们或多或少听过测试相关的东西.如果你想做DFT工程师的,测试工程师的,而对于设计/验证工程师们如果能了解下STIL文件的原理,在和DFT/测试工程师聊技术聊 ...

  8. 原来你是这样的http2......

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由mariolu发表于云+社区专栏 序言 目前HTTP/2.0(简称h2)已经在广泛使用(截止2018年8月根据Alexa流行度排名的头 ...

  9. [CPP] Object Based Class

    前言 几年前接触到一款很好玩的RPG游戏,叫作CPP.最近想着怀念一下,又不想干巴巴地去玩.于是乎,我打算写几篇攻略,主要是记录一下游戏中一些奇妙的点.游戏的第一章是面向对象程序设计,其中又分为基于对 ...

  10. ASP.NET之HTML

    1.什么是html 用来描述网页的2.开发工具我们肯定是用vs啦3.img src 图片地址 <img src="img/aa.bmp" />; 4.超链接a标签 hr ...