一.引言
多线程异步处理任务是开发中经常遇到的,但是使用newThread方式来创建单个线程的方式来处理多个线程任务有因为频繁地创建与销毁线程占用大量的资源,从而损耗应用性能。
当然谷歌官方早就注意到了这一点,所以提供了5种常见的线程池给开发者使用,当然开发者也可以根据自己业务需要创建适合自己的线程池。
下面就介绍下Android中常见的5种线程池:FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor、SingleThreadScheduledExecutor。
二.Android中常见的5种线程池
ExecutorService是Java提供的用于管理线程池的类,用于控制线程数量和重用线程作用。
1.FixedThreadPool
FixedThreadPool,它的核心线程数和最大线程数是一样的,可以把它看成是固定线程数的线程池;
public static void testFixedThreadPool() {
// 创建一个可重用固定个数的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(new Runnable() {
public void run() {
try {
// 打印正在执行的线程信息
Log.d(TAG,Thread.currentThread().getName()+ "正在被执行,第"+ index+"次");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
执行后输出Log:
2021-08-07 15:19:44.959 26761-26795/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第0次
2021-08-07 15:19:44.959 26761-26797/ D/ThreadPoolUtil: pool-1-thread-3正在被执行,第2次
2021-08-07 15:19:44.959 26761-26796/ D/ThreadPoolUtil: pool-1-thread-2正在被执行,第1次
2021-08-07 15:19:46.962 26761-26795/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第4次
2021-08-07 15:19:46.962 26761-26797/ D/ThreadPoolUtil: pool-1-thread-3正在被执行,第3次
2021-08-07 15:19:46.962 26761-26796/ D/ThreadPoolUtil: pool-1-thread-2正在被执行,第5次
2021-08-07 15:19:48.963 26761-26796/ D/ThreadPoolUtil: pool-1-thread-2正在被执行,第6次
2021-08-07 15:19:48.965 26761-26797/ D/ThreadPoolUtil: pool-1-thread-3正在被执行,第7次
2021-08-07 15:19:48.965 26761-26795/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第8次
2021-08-07 15:19:50.964 26761-26796/ D/ThreadPoolUtil: pool-1-thread-2正在被执行,第9次
通过查看日志信息分析,注意日志打印时间,每次输出3条log信息,并且同时执行的3条是不一样的线程,对应我们设置的核心线程数和最大线程数Executors.newFixedThreadPool(3),。
查看newFixedThreadPool源码:
可以看出newFixedThreadPool传入的参数就是核心线程数和最大线程数nThreads并且是一样的。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2.CachedThreadPool
CachedThreadPool,可以把它叫做可缓存线程池,它的特点是线程数可以持续增加(理论最大可达Integer.MAX_VALUE)
public static void testCachedThreadPool() {
// 创建一个可缓存线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
// try {
// sleep可明显看到使用的是线程池里面以前的线程,没有创建新的线程
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
executorService.execute(new Runnable() {
public void run() {
// 打印正在执行的线程信息
Log.d(TAG,Thread.currentThread().getName()+ "正在被执行,第"+ index+"次");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
执行后输出Log:
2021-08-07 15:21:20.986 27016-27095/ D/ThreadPoolUtil: pool-1-thread-2正在被执行,第1次
2021-08-07 15:21:20.986 27016-27094/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第0次
2021-08-07 15:21:20.988 27016-27096/ D/ThreadPoolUtil: pool-1-thread-3正在被执行,第2次
2021-08-07 15:21:20.989 27016-27098/ D/ThreadPoolUtil: pool-1-thread-5正在被执行,第4次
2021-08-07 15:21:20.989 27016-27097/ D/ThreadPoolUtil: pool-1-thread-4正在被执行,第3次
2021-08-07 15:21:20.989 27016-27100/ D/ThreadPoolUtil: pool-1-thread-7正在被执行,第6次
2021-08-07 15:21:20.989 27016-27099/ D/ThreadPoolUtil: pool-1-thread-6正在被执行,第5次
2021-08-07 15:21:20.989 27016-27101/ D/ThreadPoolUtil: pool-1-thread-8正在被执行,第7次
2021-08-07 15:21:20.989 27016-27102/ D/ThreadPoolUtil: pool-1-thread-9正在被执行,第8次
2021-08-07 15:21:20.989 27016-27103/ D/ThreadPoolUtil: pool-1-thread-10正在被执行,第9次
分析下Log信息,还是需要注意结合日志打印时间以及线程名。CachedThreadPool最大线程数能达到Integer.MAX_VALUE=2^31-1。所以10条任务对应10条线程,同时执行。
思维延申下: 打开上面的注释代码,也就是每次执行任务前暂停1s,这样有机会让前置线程执行完任务得到释放,重复利用。
执行log信息如下:
设置休眠1秒
2021-08-07 15:22:33.212 27277-27363/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第0次
2021-08-07 15:22:34.218 27277-27368/ D/ThreadPoolUtil: pool-1-thread-2正在被执行,第1次
2021-08-07 15:22:35.217 27277-27363/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第2次
2021-08-07 15:22:36.221 27277-27381/ D/ThreadPoolUtil: pool-1-thread-3正在被执行,第3次
2021-08-07 15:22:37.221 27277-27363/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第4次
2021-08-07 15:22:38.222 27277-27368/ D/ThreadPoolUtil: pool-1-thread-2正在被执行,第5次
2021-08-07 15:22:39.222 27277-27381/ D/ThreadPoolUtil: pool-1-thread-3正在被执行,第6次
2021-08-07 15:22:40.223 27277-27363/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第7次
2021-08-07 15:22:41.223 27277-27368/ D/ThreadPoolUtil: pool-1-thread-2正在被执行,第8次
2021-08-07 15:22:42.224 27277-27381/ D/ThreadPoolUtil: pool-1-thread-3正在被执行,第9次
可以看出每个线程执行任务间隔1s后在执行,线程能重复利用。
3.ScheduledThreadPool
ScheduledThreadPool,它支持定时或周期性的执行任务,有3个方法可以灵活的执行频率配置参数;
public static void testScheduledThreadPool() {
//创建一个定长线程池,支持定时及周期性任务执行——延迟执行
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
//每x秒执行一次
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
public void run() {
Log.d(TAG,Thread.currentThread().getName()+ "正在被执行");
}
}, 1, 500, TimeUnit.MILLISECONDS);
}
执行后输出Log:
2021-08-07 15:37:09.965 28901-28969/ D/ThreadPoolUtil: pool-1-thread-1正在被执行
2021-08-07 15:37:10.465 28901-28969/ D/ThreadPoolUtil: pool-1-thread-1正在被执行
2021-08-07 15:37:10.965 28901-28970/ D/ThreadPoolUtil: pool-1-thread-2正在被执行
2021-08-07 15:37:11.466 28901-28970/ D/ThreadPoolUtil: pool-1-thread-2正在被执行
2021-08-07 15:37:11.966 28901-28970/ D/ThreadPoolUtil: pool-1-thread-2正在被执行
2021-08-07 15:37:12.466 28901-28970/ D/ThreadPoolUtil: pool-1-thread-2正在被执行
2021-08-07 15:37:12.966 28901-29005/ D/ThreadPoolUtil: pool-1-thread-3正在被执行
2021-08-07 15:37:13.466 28901-29005/ D/ThreadPoolUtil: pool-1-thread-3正在被执行
2021-08-07 15:37:13.966 28901-29005/ D/ThreadPoolUtil: pool-1-thread-3正在被执行
2021-08-07 15:37:14.466 28901-28969/ D/ThreadPoolUtil: pool-1-thread-1正在被执行
....周期打印.....
可以看出是3条线程周期性的执行任务。因为设置的是核心线程数是3:Executors.newScheduledThreadPool(3)
4.SingleThreadExecutor
SingleThreadExecutor,它会使用唯一的线程去执行任务,适用于所有任务都需要按照被提交的顺序依次执行的场景;
public static void testSingleThreadExecutor() {
//创建一个单线程化的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
//结果依次输出,相当于顺序执行各个任务
Log.d(TAG,Thread.currentThread().getName()+ "正在被执行,第"+ index+"次");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
执行后输出Log:
2021-08-07 15:25:02.397 27833-27896/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第0次
2021-08-07 15:25:04.398 27833-27896/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第1次
2021-08-07 15:25:06.401 27833-27896/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第2次
2021-08-07 15:25:08.404 27833-27896/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第3次
2021-08-07 15:25:10.405 27833-27896/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第4次
2021-08-07 15:25:12.406 27833-27896/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第5次
2021-08-07 15:25:14.407 27833-27896/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第6次
2021-08-07 15:25:16.409 27833-27896/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第7次
2021-08-07 15:25:16.409 27833-27896/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第7次
2021-08-07 15:25:18.410 27833-27896/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第8次
2021-08-07 15:25:20.411 27833-27896/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第9次
从日志能分析出:一个线程在顺序的执行任务。
5.SingleThreadScheduledExecutor
SingleThreadScheduledExecutor,它和SingleThreadExecutor有些类似,它的核心线程数是1,但是最大线程数是Integer.MAX_VALUE。
public static void testSingleThreadScheduledExecutor() {
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
try {
Log.d(TAG,Thread.currentThread().getName()+ "正在被执行,第"+ index+"次");
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 3L, TimeUnit.SECONDS);
}
}
输出Log信息:
2021-08-07 15:27:22.167 28291-28321/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第0次
2021-08-07 15:27:22.170 28291-28321/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第1次
2021-08-07 15:27:22.172 28291-28321/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第2次
2021-08-07 15:27:22.174 28291-28321/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第3次
2021-08-07 15:27:22.176 28291-28321/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第4次
2021-08-07 15:27:22.178 28291-28321/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第5次
2021-08-07 15:27:22.180 28291-28321/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第6次
2021-08-07 15:27:22.182 28291-28321/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第7次
2021-08-07 15:27:22.185 28291-28321/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第8次
2021-08-07 15:27:22.187 28291-28321/ D/ThreadPoolUtil: pool-1-thread-1正在被执行,第9次
同5.SingleThreadScheduledExecutor一样。
三.ThreadPoolExecutor方法
上面介绍了5种线程池,查看源码能知道5种常用的线程池最终调用的都是ThreadPoolExecutor方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
ThreadPoolExecutor方法对应的5个参数如下:
corePoolSize: 线程池核心线程个数
maximunPoolSize: 线程池最大线程数量
keeyAliveTime: 空闲线程存活时间
TimeUnit: 存活时间单位
workQueue: 用于保存等待执行任务的阻塞队列
同样,如果我们需要自定义线程池,也可以根据自己业务需要调用ThreadPoolExecutor方法来实现。
四.拒绝策略
在ThreadPoolExecutor类中定义了四种拒绝策略:
AbortPolicy丢弃任务,并抛出异常RejectedExecutionException.默认策略。
DiscardPolicy 默默丢弃、不抛异常。
DiscardOldestPolicy 尝试去竞争第一个,也就是丢弃任务队列前面的任务,重新提交执行,失败了也不抛异常。
CallerRunsPolicy 使用调用者所在线程执行-提交任务的线程,就是哪里来的回哪里去。
最后附上一张线程池流程图,加深理解: