Android线程池使用介绍
本文主要使用kotlin,讨论Android开发中的线程池用法。
我们想使用线程的时候,可以直接创建子线程并启动
Thread { Log.d("rfDev", "rustfisher said: hello") }.start()
不想每次都创建新的子线程
如果有大量的异步任务,不想每次都创建子线程。有没有什么把子线程统一管理的方法?
遇到这样的情况,我们可以考虑线程池。线程池解决两个问题:需要执行大量异步任务的时候,减轻每个异步任务的调用开销,提高性能。另外它还能够限制和管理子线程。每个ThreadPoolExecutor都维护了一些统计数据,例如已执行的任务数量。
有大量异步任务的时候,可以考虑使用线程池。
预置线程池
代码参考 Android API 29
ThreadPoolExecutor提供了很多参数,方便开发者调控。线程池的设计者建议开发者使用以下几个工厂方法,Android中主要有5种
newCachedThreadPool()不限制数量的线程池,能自动回收线程newFixedThreadPool(int nThreads)固定数量的线程池newSingleThreadExecutor()单一的子线程newScheduledThreadPool(int corePoolSize)能执行延时任务或者周期性任务newWorkStealingPool()工作窃取线程池
实际上我们在Android Studio里输入Executors.new的时候,会跳出很多个提示选项。
Executors.new 的智能提示

可缓存线程池
用Executors.newCachedThreadPool获得一个可缓存线程池对象,然后让它执行任务。
val tp: ExecutorService = Executors.newCachedThreadPool()
tp.submit { Log.d(TAG, "rustfisher: cached线程池执行任务 3") }
可缓存线程池会在需要的时候创建新的子线程。当原有的线程可用的时候,会复用现有线程。
这个机制适用于执行多个短期异步任务。任务比较小,但是数量大。
调用execute方法会先尝试复用已有的可用线程。如果当前没有线程,会新建一个线程并把它添加到池里。
超过60秒没有使用的线程会被停止并移除。因此即便长时间不用这个线程池,也不会造成多大的开销。
定长线程池
使用newFixedThreadPool(int nThreads)示例
val fixedTp: ExecutorService = Executors.newFixedThreadPool(4)
fixedTp.submit { Log.d(TAG, "rustfisher 定长线程池执行任务") }
静态方法里传入了一个int参数nThreads,表示最大线程数量。
如果当前所有线程都在忙,又有新的任务添加进来。那么任务会在队列中等待,直到有可用的线程来处理任务。
如果有的线程遇到错误而停止了,要执行任务的话,会创建新的线程补上位置。
池里的线程会一直存活,直到线程池停止(ExecutorService#shutdown)。
单一线程池
val singleTp: ExecutorService = Executors.newSingleThreadExecutor()
singleTp.submit { Log.d(TAG, "单一线程池执行任务") }
只拥有1个子线程。任务队列不限制任务数量。如果线程遇到问题停止了,接下来又要处理任务时,会新建一个线程来处理。
它能保证任务会按顺序处理,同一时间只能处理1个任务。
单一线程池创建后,不能动态修改线程数量。不像newFixedThreadPool(1)的定长线程池可以修改线程数。
计划任务线程池
val scheduleTp: ScheduledExecutorService = Executors.newScheduledThreadPool(3)
计划任务线程池能够执行延迟任务和周期任务。
延迟任务
需要设定延时与时间单位
scheduleTp.schedule({ Log.d(TAG, "计划任务1 runnable") }, 300, TimeUnit.MILLISECONDS)
scheduleTp.schedule(Callable { Log.d(TAG, "计划任务2 callable") }, 400, TimeUnit.MILLISECONDS)
周期任务
主要涉及到2个方法scheduleAtFixedRate和scheduleWithFixedDelay。
假设任务时间小于周期时间,则按给定周期时间来进行。这两个方法表现一致。
假设任务执行时间大于周期时间,这两个方法有点不同
scheduleAtFixedRate执行完上一个任务后,用时超过了周期时间,会立刻执行下一个任务。scheduleWithFixedDelay在上一个任务执行完毕后,还会等待周期时间,再去执行下一个任务。
工作窃取线程池
Android SDK 大于等于24,有一种新的线程池,暂且称为“工作窃取线程池”,或者叫“灵活调度线程池”。
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
    Executors.newWorkStealingPool()
}
线程池维护足够的线程来支持给定的并行度(parallelism level),可能会用多个队列来减少争用。
并行度对应的是活跃的线程最大数,或者能处理任务的线程最大数。
线程的实际数量可能会动态增减。工作窃取线程池不保证按提交顺序来处理任务。
执行任务
执行任务的时候可以传入Runnable和Callable,前面用的都是Runnable。
用Callable的例子
tp.submit(Callable { "OK" })
无返回值任务的调用
无返回值任务用Callable和Runnable都行。
val tp: ExecutorService = Executors.newCachedThreadPool()
tp.submit { Log.d(TAG, "rustfisher: cached线程池submit runnable") }
tp.execute { Log.d(TAG, "rustfisher: cached线程池execute runnable") }
tp.submit(Callable { Log.d(TAG, "rustfisher: cached线程池submit callable") })
tp.shutdown() // 最后记得用完后停掉线程池
有返回值任务的调用
有返回值的任务需要Callable接口。
submit
调用submit方法时会返回一个Future对象。通过Future的get()方法可拿到返回值。这里需要注意get()是阻塞的,完成任务后,能拿到返回值。
val tp: ExecutorService = Executors.newCachedThreadPool()
val future = tp.submit(Callable {
    return@Callable "callable的返回值"
})
Log.d(TAG, "future get之前 isDone: ${future.isDone}, isCancelled: ${future.isCancelled}")
val res = future.get()
Log.d(TAG, "future get之后 isDone: ${future.isDone}, isCancelled: ${future.isCancelled}")
Log.d(TAG, "future get: $res")
运行log
future get之前 isDone: false, isCancelled: false
future get之后 isDone: true, isCancelled: false
future get: callable的返回值
invokeAll
对于列表里的任务,可以使用invokeAll(Collection<? extends Callable<T>> tasks),返回一个Future的列表。
作为对比,给其中一个任务加上延时。
invokeAll示例
    val tp: ExecutorService = Executors.newFixedThreadPool(5)
    val callList = arrayListOf<Callable<String>>(
            Callable {
                Log.d(TAG, "task1 ${Thread.currentThread()}")
                return@Callable "rust"
            },
            Callable {
                Log.d(TAG, "task2 ${Thread.currentThread()}")
                Thread.sleep(1500) // 加上延时
                return@Callable "fisher"
            },
            Callable {
                Log.d(TAG, "task3 ${Thread.currentThread()}")
                return@Callable "列表里面的任务"
            },
    )
    Log.d(TAG, "invokeAll 准备提交任务")
    val futureList = tp.invokeAll(callList)
    Log.d(TAG, "invokeAll 已提交任务")
    futureList.forEach { f ->
        Log.d(TAG, "任务列表执行结果 ${f.get()}") // 这里会阻塞 别在ui线程里get
    }
运行log,可以看到提交任务后,经过延时,拿到了运行结果。注意看invokeAll前后的时间。invokeAll会阻塞当前线程。使用的时候必须小心,不要在ui线程中调用。
    2021-09-11 14:40:07.062 16914-16914/com.rustfisher.tutorial2020 D/rfDevTp: invokeAll 准备提交任务
    2021-09-11 14:40:07.063 16914-19230/com.rustfisher.tutorial2020 D/rfDevTp: task1 Thread[pool-4-thread-1,5,main]
    2021-09-11 14:40:07.063 16914-19231/com.rustfisher.tutorial2020 D/rfDevTp: task2 Thread[pool-4-thread-2,5,main]
    2021-09-11 14:40:07.063 16914-19232/com.rustfisher.tutorial2020 D/rfDevTp: task3 Thread[pool-4-thread-3,5,main]
    2021-09-11 14:40:08.563 16914-16914/com.rustfisher.tutorial2020 D/rfDevTp: invokeAll 已提交任务
    2021-09-11 14:40:08.563 16914-16914/com.rustfisher.tutorial2020 D/rfDevTp: 任务列表执行结果 rust
    2021-09-11 14:40:08.563 16914-16914/com.rustfisher.tutorial2020 D/rfDevTp: 任务列表执行结果 fisher
    2021-09-11 14:40:08.563 16914-16914/com.rustfisher.tutorial2020 D/rfDevTp: 任务列表执行结果 列表里面的任务
提交了3个任务,在3个不同的子线程中执行。
invokeAny
invokeAny(Collection<? extends Callable<T>> tasks)也是接收Callable集合。
然后返回最先执行结束的任务的值,其它未完成的任务将被正常取消掉不会有异常。
invokeAny示例
    val tp: ExecutorService = Executors.newCachedThreadPool()
    val callList = arrayListOf<Callable<String>>(
            Callable {
                Thread.sleep(1000) // 设计延时
                return@Callable "rust"
            },
            Callable {
                Thread.sleep(400)
                return@Callable "fisher"
            },
            Callable {
                Thread.sleep(2000)
                return@Callable "列表里面的任务"
            },
    )
    Log.d(TAG, "invokeAny 提交任务")
    val res = tp.invokeAny(callList)
    Log.d(TAG, "执行结果 $res")
    2021-09-11 14:04:55.253 14066-14066/com.rustfisher.tutorial2020 D/rfDevTp: invokeAny 提交任务
    2021-09-11 14:04:55.654 14066-14066/com.rustfisher.tutorial2020 D/rfDevTp: 执行结果 fisher
观察log可以看到,最后执行的是“fisher”这个任务。
停止线程池
使用完毕后,记得终止线程池
/*ExecutorService*/ shutdown()
shutdownNow()
shutdown()在已提交的任务后面创建一个停止命令,并且不再接受新的任务。如果线程池已经停止了,调用这个方法将不生效。
shutdownNow()方法尝试停止所有执行中的任务,停下等待中的任务。并且返回等待执行的任务列表List<Runnable>。
Android线程池使用介绍的更多相关文章
- Android(java)学习笔记267:Android线程池形态
		
1. 线程池简介 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项任务所需时间为:T1 创建线程时间, ...
 - android 线程池的使用
		
转自http://www.trinea.cn/android/java-android-thread-pool/ Java(Android)线程池 介绍new Thread的弊端及Java四种线程池的 ...
 - Android(java)学习笔记211:Android线程池形态
		
1. 线程池简介 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项任务所需时间为:T1 创建线程时间, ...
 - android线程池ThreadPoolExecutor的理解
		
android线程池ThreadPoolExecutor的理解 线程池 我自己理解看来.线程池顾名思义就是一个容器的意思,容纳的就是ThreadorRunable, 注意:每一个线程都是需要CPU分配 ...
 - 【转】线程池体系介绍及从阿里Java开发手册学习线程池的正确创建方法
		
jdk1.7中java.util.concurrent.Executor线程池体系介绍 java.util.concurrent.Executor : 负责线程的使用与调度的根接口 |–Execut ...
 - juc线程池原理(四): 线程池状态介绍
		
<Thread之一:线程生命周期及五种状态> <juc线程池原理(四): 线程池状态介绍> 线程有5种状态:新建状态,就绪状态,运行状态,阻塞状态,死亡状态.线程池也有5种状态 ...
 - Android 线程池概念及使用
		
一:使用线程池的原因 在android开发中经常会使用多线程异步来处理相关任务,而如果用传统的newThread来创建一个子线程进行处理,会造成一些严重的问题: 在任务众多的情况下,系统要为每一个任务 ...
 - 最强大的Android线程池框架
		
背景 大家都知道在我们的开发中永远都离不开多线程,对于我们为什么要使用多线程,多线程的使用和多线程的一些基础知识这里我们就不讲了,有兴趣的朋友可以去看一下博主之前的几篇文章: 线程你真的了解它吗 这才 ...
 - 线程池的介绍和使用,以及基于jvmti设计非入侵监控
		
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 五常大米好吃! 哈哈哈,是不你总买五常大米,其实五常和榆树是挨着的,榆树大米也好吃, ...
 
随机推荐
- js hook
			
//cookie hook (function () { 'use strict'; var cookie_cache = document.cookie; Object.defineProperty ...
 - BSTestRunner增加历史执行记录展示和重试功能
			
之前对于用例的失败重试,和用例的历史测试记录存储展示做了很多的描述呢,但是都是基于各个项目呢,不方便使用,为了更好的使用,我们对这里进行抽离,抽离出来一个单独的模块,集成到BSTestRunner中, ...
 - 为了彻底搞懂 hashCode,我钻了一下 JDK 的源码
			
今天我们来谈谈 Java 中的 hashCode() 方法--通过源码的角度.众所周知,Java 是一门面向对象的编程语言,所有的类都会默认继承自 Object 类,而 Object 的中文意思就是& ...
 - vue 源码详解(二): 组件生命周期初始化、事件系统初始化
			
vue 源码详解(二): 组件生命周期初始化.事件系统初始化 上一篇文章 生成 Vue 实例前的准备工作 讲解了实例化前的准备工作, 接下来我们继续看, 我们调用 new Vue() 的时候, 其内部 ...
 - Docker部署ELK之部署logstash7.6.0(4)
			
前言: logstash 和filebeat都具有日志收集功能,filebeat更轻量,占用资源更少,但logstash 具有filter功能,能过滤分析日志.一般结构都是filebeat采集日志,然 ...
 - Spring全家桶--单数据源的配置
			
前言 spring数据源的配置网络上有很多例子,这里我也来介绍一下单数据源配置的例子,基于SpringBoot的方式和原生的Spring的方式. 一.生成项目骨架(SpringBoot),运行一个简单 ...
 - nacos项目搭建(服务提供者,服务消费者)
			
spring cloud ablibaba 版本说明 https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明 启动nacos服务 官网: h ...
 - maven打包war,导入本地jar包
			
方法1: 一 . 在项目根目录创建lib文件夹,把jar放入lib文件夹中 二 . 在项目中使用本地jar pom文件配置如下: <properties> <project.buil ...
 - taro小程序展示富文本
			
在微信小程序下会用到wxParse这个东西来达到html转换wxml的效果, taro小程序官方也给出了示例,地址 这里封装成自己的组件: import Taro, { Component } fro ...
 - 如何选择Spring cloud和 Spring Boot对应的版本
			
如何选择Spring cloud和 Spring Boot对应的版本 首先,我们进入Spring Cloud官网,查询Spring cloud的版本和对应的Spring Boot版本 打开Spring ...