一.概述
在日常开中,如果经常使用到线程的话,如何快捷创建和关闭回收线程是一件较为麻烦的事情,因此 阿里巴巴的Java规范插件 一直都是建议使用线程池来管理线程
使用线程池有那些好处?
- 复用存在的线程,减少线程的创建,减少线程的开销,进一步提高了效率
- 通过阻塞队列来控制最大的线程并发数,让内存等消耗限制在一个合理的范围内
二.线程池的相关参数
我们要怎么使用线程池呢?我们先来看下线程池参数最全面的构造方法吧:
/**
* 创建一个线程池
*
*
* @param corePoolSize 核心线程数量,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
* 当线程池里面的线程数量超过corePoolSize就会启动回收操作
*
* @param maximumPoolSize 线程池里面的最大线程数
*
* @param keepAliveTime 表示线程空闲时多久才将其回收,只有当线程池里面的线程数量超过corePoolSize才会生效,
* 它会让线程池里面的线程数量尽量等于或接近corePoolSize
*
* @param unit keepAliveTime的时间单位
*
* @param workQueue 阻塞队列,用来存储等待执行的任务,等待线程池里面有空闲的线程来执行任务
*
* @param threadFactory 用来生产线程的工厂
*
* @param handler 当任务无法处理时的策略
*
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
在明白了相关参数配置后,我们就可以开始依据自己需求来定制线程池了,可能你还是有点无从下手的感觉,没事,我们可以先看下其他人是怎么定制的,比方说看下 Glide 是怎么定制自己内部的线程池的
三.Glide 中定制的几个线程池
我们可以在 Glide 的 GlideExecutor
里面找到它定制的线程池,Glide 定制了 4 个 线程池
1.newDiskCacheExecutor
这是用于执行本地缓存任务的线程池
public static GlideExecutor newDiskCacheExecutor(
int threadCount, String name, UncaughtThrowableStrategy uncaughtThrowableStrategy) {
return new GlideExecutor(
new ThreadPoolExecutor(
threadCount /* corePoolSize,数值为1 */,
threadCount /* maximumPoolSize,数值为1 */,
0 /* keepAliveTime为0 */,
TimeUnit.MILLISECONDS,
/*无序的阻塞队列,先进先出 */
new PriorityBlockingQueue<Runnable>(),
/*一个创建优先级为最低的后台线程的线程工厂*/
new DefaultThreadFactory(name, uncaughtThrowableStrategy, true)));
}
可以看出,newDiskCacheExecutor
这个线程池里面的核心线程1个,线程池最多持有线程数是1,线程的保活时间为0,线程工厂生产后台线程,可以说整个线程池由始至终就只有一个线程来循环使用来执行 Glide 的本地缓存任务
2.newAnimationExecutor
这是用于执行解析gif图片的线程池(它和 Glide 里面的动画逻辑没啥关系) , Glide 里面的 RequestOptions
里面有个 dontAnimate
,就是用来关闭配置整个线程池,从而将关闭 Glide 的 gif 图片加载功能(动画加载逻辑也会一同关闭)
//得到cpu的核数,返回值小于等于4
public static int calculateBestThreadCount() {
if (bestThreadCount == 0) {
bestThreadCount =
Math.min(MAXIMUM_AUTOMATIC_THREAD_COUNT,/*这个值是 4 */, RuntimeCompat.availableProcessors());
}
return bestThreadCount;
}
public static GlideExecutor newAnimationExecutor() {
int bestThreadCount = calculateBestThreadCount();
int maximumPoolSize = bestThreadCount >= 4 ? 2 : 1;
return newAnimationExecutor(maximumPoolSize, UncaughtThrowableStrategy.DEFAULT);
}
public static GlideExecutor newAnimationExecutor(
int threadCount, UncaughtThrowableStrategy uncaughtThrowableStrategy) {
return new GlideExecutor(
new ThreadPoolExecutor(
0 ,/* corePoolSize为0 */
threadCount,/* maximumPoolSize,数值为2或者1 */
KEEP_ALIVE_TIME_MS,/* keepAliveTime为10s的毫秒数 */
TimeUnit.MILLISECONDS,
/*无序的阻塞队列,先进先出 */
new PriorityBlockingQueue<Runnable>(),
/*一个创建优先级为最低的后台线程的线程工厂*/
new DefaultThreadFactory(ANIMATION_EXECUTOR_NAME,uncaughtThrowableStrategy,true)));
}
可以看出,newAnimationExecutor
这个线程池里面的核线程数量是0,线程池最多持有线程数是依据cpu核数来,高配手机是2个,低配手机是1个,线程工厂生产后台线程,这个线程池没有核心线程,只要将阻塞队列里面的任务全部完成后,线程也随即给回收掉,绝不会再消耗更多的内存
3.newSourceExecutor 和 newUnlimitedSourceExecutor
这两个线程池都是用于执行网络请求任务的,至于使用哪一个,则交给外层来配置了
public static GlideExecutor newSourceExecutor() {
return newSourceExecutor(
calculateBestThreadCount(),
DEFAULT_SOURCE_EXECUTOR_NAME,
UncaughtThrowableStrategy.DEFAULT);
}
public static GlideExecutor newSourceExecutor(
int threadCount, String name, UncaughtThrowableStrategy uncaughtThrowableStrategy) {
return new GlideExecutor(
new ThreadPoolExecutor(
threadCount /* corePoolSize至少为4 */,
threadCount /* maximumPoolSize至少为4 */,
0 /* keepAliveTime为0 */,
TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(),
/*一个创建优先级为最低的后台线程的线程工厂*/
new DefaultThreadFactory(name, uncaughtThrowableStrategy, false)));
}
.............
public static GlideExecutor newUnlimitedSourceExecutor() {
return new GlideExecutor(new ThreadPoolExecutor(
0/* corePoolSize为0 */,
Integer.MAX_VALUE /* maximumPoolSize为Integer.MAX_VALUE */,
KEEP_ALIVE_TIME_MS /* keepAliveTime为 10s */,
TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>() /* 这个阻塞队列不会保存任务,会立即创建新的线程来执行任务 */,
/*一个创建优先级为最低的后台线程的线程工厂*/
new DefaultThreadFactory(
SOURCE_UNLIMITED_EXECUTOR_NAME,
UncaughtThrowableStrategy.DEFAULT,
false)));
}
应该都看出来这两个线程池的区别了
-
newSourceExecutor : 核心线程数量和线程池最多持有线程数都是依据 CPU 核数来决定的,至少是4个,线程的保活时间为0,线程工厂设置为后台线程,它一直固定着最多 4 个线程来执行网络请求任务
-
newUnlimitedSourceExecutor : 核心线程数量为 0,线程池最大线程数是 Integer.MAX_VALUE,线程的保活时间为 10s ,线程工厂设置为后台线程,对 Java 的 4 大线程池 API 熟悉的话,就会发现这个其实就是个 改版的
newCachedThreadPool
,它会在第一时间去创建新的 线程来执行任务,在 10s (newCachedThreadPoo
设置是 60s 后开始回收) 后开始回收线程,缺点也很明显,如果一次性加入大 量的任务就会创建大量的线程,有 OOM 的风险,使用这个线程池必须非常小心
因此没有必要的话,建议还是使用 newSourceExecutor 执行网络请求任务
四.如何关闭线程池?
我们或者会遇到需要关闭线程池的情况,虽然线程池已经给我们提供了 shutdown
和 shutdownNow
两个方法,但事实上这个并不能确保可以关闭线程池
Glide 提供了 tearDown
用于强行停止线程池,我们可以追踪进去看下它是怎么确保线程池的关闭:
public static void shutdownAndAwaitTermination(ExecutorService pool) {
long shutdownSeconds = 5;
pool.shutdownNow();
try {
if (!pool.awaitTermination(shutdownSeconds, TimeUnit.SECONDS)) {
pool.shutdownNow();
if (!pool.awaitTermination(shutdownSeconds, TimeUnit.SECONDS)) {
throw new RuntimeException("Failed to shutdown");
}
}
} catch (InterruptedException ie) {
pool.shutdownNow();
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
}
我们可以追踪倒上面的 shutdownAndAwaitTermination
方法,可以看到如果遇到 shutdownNow
失败的情况,就抛出异常来关闭线程池