Android 开发中耗时任务应该放在子线程中进行,否则会阻塞 UI 造成 ANR。但是如果直接创建子线程,阿里规约会提示:
关于禁止直接创建线程的原因如图,不再赘述。
当我们转而选择线程池的时候,JDK 提供了一个简单的工厂模式来创建:Executors
。
使用非常简单,四个方法可以创建四个不同类型的线程池,基本满足大部分线程池的使用需求。
1//创建固定数目线程的线程池
2Executors.newFixedThreadPool(int nThreads);
3
4//创建一个唯一线程的线程池
5Executors.newSingleThreadExecutor()
6
7//创建一个可缓存的线程池
8Executors.newCachedThreadPool()
9
10//创建一个支持定时及周期性的任务执行的线程池
11Executors.newScheduledThreadPool(int corePoolSize)
但是如果使用了,会发现阿里规约再次禁止:
约定禁止的原因如下:
对于 newFiexedThreadPool
和 newSingleThreadExecutor
:
1public static ExecutorService newFixedThreadPool(int nThreads) {
2 return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
3}
1public static ExecutorService newSingleThreadExecutor() {
2 return new FinalizableDelegatedExecutorService
3 (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()));
4}
原因是请求队列为 LinkedBlockingQueue
的默认实现,跟踪代码可以看到:
1public LinkedBlockingQueue() {
2 this(Integer.MAX_VALUE);
3}
在其默认实现中,队列长度为 Integer
最大值。
对于 newCachedThreadPool
和 newScheduledThreadPool
:
1public static ExecutorService newCachedThreadPool() {
2 return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());
3}
1public ScheduledThreadPoolExecutor(int corePoolSize) {
2 super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue());
3}
原因是最大线程数为 Integer
最大值。
这四个方法如果直接使用默认模式,无一幸免均可能因为线程太多而造成 OOM。
综上,我们需要使用 ThreadPoolExecutor
手动创建线程池。
ThreadPoolExecutor
一般需要关心的参数如下:
corePoolSize
:核心线程数量maximumPoolSize
:最大线程数量keepAliveTime
和 unit
:非核心线程空闲后的存活时间workQueue
:保存等待执行的任务的阻塞队列handler
:队列和最大线程都满了之后的饱和策略其中 workQueue
的常用类型为:
SynchronousQueue
:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它LinkedBlockingQueue
:这个队列接收到任务的时候,如果当前线程数等于核心线程数,则进入队列等待。由于这个队列默认没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了 maximumPoolSize
的设定失效,因为总线程数永远不会超过 corePoolSize
ArrayBlockingQueue
:可以限定队列的长度,接收到任务的时候,如果当前线程数等于核心线程数,则入队等候,如果队列已满,则新建非核心线程执行任务DelayQueue
:队列内元素必须实现 Delayed 接口,这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务线程池执行任务时的策略如下:
corePoolSize
,则新建一个线程(核心线程)执行任务corePools
,则将任务移入队列等待maximumPoolSize
,就会由 handler
执行饱和策略(一般是抛出异常)对于现在的开发来说最简单的方案应该是换 Kotlin 使用协程,Kotlin 内置了多个线程池给我们用:
Dispatchers.Main
- 主线程,用于界面更新。Dispatchers.IO
- I/O 线程池。用于读写数据库和网络请求。Dispatchers.Default
- CPU 线程池。用于重 CPU 操作。