关于线程我不想再说什么,感兴趣的同学可以看我之前写过的一篇文章:Android-多线程,这里对线程有一个比较详细的解释。
我们今天直入重点:聊意料我们常用的线程池.
一:
1.什么是线程池?
听名字也不难理解,线程池就是线程的集合,用来控制管理线程,控制并发数等,减少了线程创建和销毁的次数。提高线程的复用率,降低性能的损耗。
2.有哪些线程池呢?
首先我们先从源码的角度开始分析,先创建一个线程池,然后一步步查看它的源码,分析其关系。
直接上图:
然后一步步查看:从最上面的接口开始。
首先。Executor是一个类的实现接口,里面有一个执行方法:execute().,这就是线程池的起源,属于线程的一个执行工具,并不是真正的线程池。
如图,我们可以看到ExecutorService接口继承自我们之前的Executor工具类,它里面有好多的api方法,是真正的线程池接口。
然后呢,AbstractExecutorService是一个抽象方法,它实现了ExecutorService接口,可以实现其抽象方法, 也可以进行自定义。
最后ThreadPoolExecutor才是我们的具体实现类,Android中的线程池都是 直接或间接通过配置ThreadPoolExecutor 来实现不同的线程池。
我们来看一下它的构造方法:
其实ThreadPoolExecutor有四种构造方法,我们举例其中最多的一个讲一下。
(1) //核心线程数
int corePoolSize,
注:线程池新建线程的时候,如果当前线程总数小于 corePoolSize ,则新建的是核心线程;如果超过corePoolSize,则新建的是非核心线程。
(2) //最大线程数,活动线程数量超过它,后续任务就会自动排队
int maximumPoolSize
线程总数= 核心线程数 + 非核心线程数。
(3) //超时时长,非核心线程如果长时间闲置,超过这个时长便被回收 ,但如果设置allowCoreThreadTimeOut = true,则会作用于核心线程 ,超过时长也会被回收。
long keepAliveTime,
(4) //枚举类型,设置keepAliveTime的单位,MILLISECONDS : 毫秒 、SECONDS : 秒、MINUTES : 分、HOURS : 小时、DAYS : 天
TimeUnit unit,
//缓冲任务队列,线程池的execute方法会将Runnable对象存储起来,当所有的核心线程都有活干,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。
BlockingQueue workQueue,
(5) //线程工厂接口,只有一个new Thread(Runnable r)方法,可以认为是线程池创建新线程
BlockingQueue有四种队列类型:(1)SynchronousQueue:(同步队列)
(2)LinkedBlockingQueue(链表阻塞队列)
(3)ArrayBlockingQueue(数组阻塞队列)
(4)DelayQueue(延迟队列)
SynchronousQueue:(同步队列)这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,但如果所有的线程都在工作呢?这种情况下,SynchronousQueue就会新建一个线程来处理这个任务。所以为了保证不出现(线程数达到了maximumPoolSize而不能新建线程)的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大,去规避这个使用风险。
LinkedBlockingQueue(链表阻塞队列):这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
ArrayBlockingQueue(数组阻塞队列):可以限定队列的长度(既然是数组,那么就限定了大小),接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
DelayQueue(延迟队列):队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
(6) //创建线程的方式
ThreadFactory threadFactory 这是一个接口,new它的时候需要实现他的Thread newThread(Runnable r)方法。
(7) // 这个主要是用来抛异常的,如果线程无法执行新任务一般会抛一个RejectedExecutionException异常。
RejectedExecutionHandler handler
二:ok,讲完了这些,我们来看看如何使用线程池?
线程池新添加了任务,那么线程池是如何运行的呢,总结:
1:如果线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
2:如果线程数量达到了corePools,则将任务移入队列等待
3:如果队列已满,新建线程(非核心线程)执行任务
4:如果队列已满,总线程数又达到了maximumPoolSize,就会由RejectedExecutionHandler抛出异常
但我们不需要这么费劲,java已经给我们提供了四种线程池,,来搞一下。
第一种:
A:定长线程池 newFixedThreadPool ,只有核心线程,数量固定,不会被回收。可控制线程最大并发数,超出的线程会在队列中等待。其中nThreads参数是我们要创建几个线程。如果所有线程都出于运行状态,提交额外的任务,他们会在队列中等待,直到有一个线程可用为止。
使用:
第二种:
B:缓存线程池 newCacheThreadPool,只有非核心线程,最大线程数不做限制,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。空闲线程超过60秒会被终止并从线程池里移除缓存。
使用:
第三种:
C:定长任务线程池,ScheduledExecutorService.支持定时及周期性任务执行。
corePoolSize 参数是指在池中保留的线程数,即使它们是空闲的。这个函数最终会返回一个新创建的调度线程池.
使用:
第四种:
D:newSingleThreadExecutor 创建一个单线程的线程池,用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个单独的线程终止是因为在执行前异常或者终止,若需要执行后续的任务,那么就需要一个新的去替代它,返回的是一个重新创建的单线程去执行。
使用:
突然觉得直接写用法,不来个demo,是不是小伙伴不容易理解呀。那好。我们以缓存线程池为例.来个场景演示。大家都知道下载文件吧,我们就整这个。多线程下载多个文件。
上一波图:
咳咳,初始化UI就.......省了吧。
效果:
点击下载文件可以看到是同一个线程池,两个线程平行。
文件A和文件C是同一个线程在下载,也就是在线程thread-1下载文件A完毕后,点击下载文件C,并没有在重新在创建线程,而是复用了文件A下载的thread-1继续下载文件C。
好了就这么多,拜拜。