线程池有关知识整理
前置知识
线程基本概念,线程基本概念和Android启动线程的三种方式(点击查看)
new Thread()的弊端
- 总是new Thread()开启线程,线程执行完会被回收,导致频繁的GC
- 多线程缺乏统一管理,各线程之间互相竞争
- 无法实现停止线程(如果一个item滑出页面,则要停止该item上图片的加载,但是如果使用这种方式来创建线程,则无法实现线程停止执行)
使用线程池的好处
- 提高线程的复用性,避免频繁创建线程进而导致频繁的GC
- 控制线程并发数,合理利用资源
- 线程可控性,比如可以定时执行和取消执行某个线程的任务
线程池的实现
- Android中的线程池其实源于Java,Android开发中线程池的使用和Java中线程池的使用基本一致
- Java中和线程有关的东东叫做Executor,Executor本身是一个接口
- 这个接口有一个非常有用的实现类叫做ThreadPoolExecutor
- Android中常用的线程池都是通过对ThreadPoolExecutor进行不同配置来实现的
类的继承结构
Windows:Ctrl+H
Mac:Control+H
关于ThreadPoolExecutor
ThreadPoolExecutor有四个重载的构造方法,我们这里来说说参数最多的那一个重载的构造方法,这样大家就知道其他方法参数的含义了
构造函数源码:
1 | /** |
这里是7个参数(我们在开发中用的更多的是5个参数的构造方法),OK,那我们来看看这里七个参数的含义:
- corePoolSize:核心线程的数量
- maximumPoolSize:最大线程数量
- keepAliveTime:非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长
- unit:第三个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等
- workQueue:线程池中的任务队列,该队列存储已经被提交,但是尚未执行的任务,存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
- threadFactory 为线程池提供创建新线程的功能,这个我们一般使用默认即可
- handler 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。
关于workQueue
- workQueue是一个BlockingQueue类型
- BlockingQueue是一个特殊的队列
- 从BlockingQueue中取数据时,
- 如果BlockingQueue是空的,则取数据的操作会进入到阻塞状态
- 当BlockingQueue中有了新数据时,这个取数据的操作又会被重新唤醒
- 如果BlockingQueue中的数据已经满了,往BlockingQueue中存数据的操作又会进入阻塞状态(直到BlockingQueue中又有新的空间,存数据的操作又会被冲洗唤醒
- BlockingQueue的种类
- ArrayBlockingQueue:这个表示一个规定了大小的BlockingQueue,ArrayBlockingQueue的构造函数接受一个int类型的数据,该数据表示BlockingQueue的大小,存储在ArrayBlockingQueue中的元素按照FIFO(先进先出)的方式来进行存取。
- LinkedBlockingQueue:这个表示一个大小不确定的BlockingQueue,在LinkedBlockingQueue的构造方法中可以传一个int类型的数据,这样创建出来的LinkedBlockingQueue是有大小的,也可以不传,不传的话,LinkedBlockingQueue的大小就为Integer.MAX_VALUE
- PriorityBlockingQueue:这个队列和LinkedBlockingQueue类似,不同的是PriorityBlockingQueue中的元素不是按照FIFO来排序的,而是按照元素的Comparator来决定存取顺序的(这个功能也反映了存入PriorityBlockingQueue中的数据必须实现了Comparator接口)。
- SynchronousQueue:这个是同步Queue,属于线程安全的BlockingQueue的一种,在SynchronousQueue中,生产者线程的插入操作必须要等待消费者线程的移除操作,Synchronous内部没有数据缓存空间,因此我们无法对SynchronousQueue进行读取或者遍历其中的数据,元素只有在你试图取走的时候才有可能存在。我们可以理解为生产者和消费者互相等待,等到对方之后然后再一起离开。
ThreadPoolExecuto线程池执行任务时:
execute一个线程之后:
- 如果线程池中的线程数未达到核心线程数,则会立马启用一个核心线程去执行
- 如果线程池中的线程数已经达到核心线程数,且workQueue未满,则将新线程放入workQueue中等待执行
- 如果线程池中的线程数已经达到核心线程数但未超过非核心线程数,且workQueue已满,则开启一个非核心线程来执行任务
- 如果线程池中的线程数已经超过非核心线程数,则拒绝执行该任务
如何配置这些参数(参考AsycTask)
- 核心线程数为手机CPU数量+1(cpu数量获取方式Runtime.getRuntime().availableProcessors())
- 最大线程数为手机CPU数量×2+1
- 线程队列的大小为128
系统帮我们配置好的线程池四种对比
FixedThreadPool
核心线程数量固定的线程池
1
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
源码:
1
2
3
4
5public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}- 核心线程数和最大线程数一样
- FixedThreadPool中没有非核心线程,所有的线程都是核心线程
- 线程的超时时间为0
- 核心线程即使在没有任务可执行的时候也不会被销毁(这样可让FixedThreadPool更快速的响应请求)
- LinkedBlockingQueue却没有参数,这说明线程队列的大小为Integer.MAX_VALUE(2的31次方减1)
- 所有的核心线程都在执行任务的时候,新的任务只能进入线程队列中进行等待,直到有线程被空闲出来
SingleThreadExecutor
singleThreadExecutor和FixedThreadPool很像,不同的就是SingleThreadExecutor的核心线程数只有1
1
2
3
4
5
6public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}最大好处就是可以避免我们去处理线程同步问题
只有一个核心线程,确保所有任务都在同一线程中按顺序完成。因此不需要处理线程同步的问题。
FixedThreadPool的参数传个1效果一样
CachedThreadPool
最大的优势是它可以根据程序的运行情况自动来调整线程池中的线程数量
1
2
3
4
5public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}CachedThreadPool中是没有核心线程的,但是它的最大线程数却为Integer.MAX_VALUE
它是有线程超时机制的,超时时间为60秒
最大线程数为无限大
添加一个新任务进来的时候,如果线程池中有空闲的线程,则由该空闲的线程执行新任务
没有空闲线程,则创建新线程来执行任务
CachedThreadPool中没有新任务的时候,它里边所有的线程都会因为超时而被终止
任务队列SynchronousQueue相当于一个空集合,导致任何任务都会被立即执行
可以在有大量任务请求的时候使用CachedThreadPool
ScheduleThreadPool
具有定时定期执行任务功能的线程池
1
2
3
4
5public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}核心线程数量是固定的
- 非核心线程是无穷大,当非核心线程闲置时,则会被立即回收。
- 支持延迟执行任务;定时执行任务;延迟定时执行任务
线程池的其他功能
- shutDown():关闭线程池,不影响已经提交的任务
- shutDownNow():关闭线程池,并尝试终止正在执行的线程
- allowCoreThreadTimeOut(boolean value):允许核心线程闲置超时被回收
- submit:一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值
线程池简单封装
ThreadPoolManager.java
1 | import java.util.concurrent.Executors; |
再次比较四种常见的线程池详细比较
不管哪种线程池,都是靠ThreadPoolExecutor的七个参数的构造来实现的
核心线程 | 最大线程 | 特点 | ||
---|---|---|---|---|
FixThreadPool | 有 | 无(非核心线程) | 不会被回收 | 只有固定数量的核心线程,线程都活动时,新任务等待 |
SingleThreadPool | 1 | 无(非核心线程) | 不会被回收 | 所有任务都在统一线程执行,不需要处理线程同步问题 |
CachedThreadPool | 无 | Integer.MAX_VALUE | 超时时间60秒 | 任何任务立即执行 |
ScheduledThreadPool | 有 | Integer.MAX_VALUE | 非核心线程闲置,立即回收 | 可以延迟和定时执行 |
相关资料: