多线程的开启数量应该受到 CPU 核心数的限制,你起码不能超过这个吧,但是要处理的东西太多,不用那么多线程就没办法解决这些问题,我们在kotlin中引入协程,我的一个同学在Android Studio中开了几十万个协程还是几百万个协程才把他的16个G撑爆,足以见证协程的轻量
在java中我们引入线程池来解决这种
Android中的线程池都是直接或间接通过配置ThreadPoolExecutor来实现的
所以我们先讲ThreadPoolExecutor
ThreadPoolExecutor的构造方法很多,但是最常用的还是这个
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
我们分别解析一下这6个参数
这个是核心线程数,举一个例子,比如有一个公司有很多人,最核心的就那么多人。
当公司进行裁员的时候,优先猜的是那些不重要的,这些核心人员很能裁他们
corePoolSize同理,默认情况下,核心线程会在线程池中一直存活下去,即使它们处于闲置状态
但是这是默认情况下,如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true情况就不一样了,闲置的核心线程在等待新任务到来会有超时策略,这个时间间隔由keepAliveTime指定,当等待的时间超过keepAliveTime,核心线程就会被终止
这个是线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续的新任务将被阻塞
我们刚才说的corePoolSize小于等于maximumPoolSize的大小
这个我们刚才说过这个,当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,核心线程的闲置时间超过这个时间之后就会被终止。但是如果没有设置这个的话,它是判断非核心线程的闲置时间,如果非核心线程的闲置时间超过了这个时间,该非核心线程就会被终止
unit
参数用于指定 keepAliveTime
的时间单位。
workQueue称为任务队列,在《Android进阶之光》花了大量篇幅来介绍它,在那本书中被叫做阻塞队列,通过线程池的execute方法提交的Runnable对象会存储到这个队列中
有这几个阻塞队列
我们常用的是第一个和第二个
即ArrayBlockingQueue与LinkedBlockingQueue
我们要记得ArrayBlockingQueue是有界队列
LinkedBlockingQueue是有界队列但是,如果你自己构造一个LinkedBlockingQueue但是没有指定它的容量大小,他就会默认一个无限大的容量
所以如果你没有指定LinkedBlockingQueue那么它就是一个无界队列,如果你指定了它的大小,它就是一个有界队列
阻塞队列常用于生产者消费者场景,生产端负责生产,生产的东西发往消费者端。其中生产者端就是往这个阻塞队列添加元素的线程,消费者端就是从队列中拿出元素的线程
常见的阻塞场景主要有2个
线程工厂,为线程池提供创建新线程的功能,ThreadFactory是一个接口,它只有一个方法
Thread newThread(Runnable r)
线程池的工作流程主要是
如果线程池中的线程数量未达到核心线程数量,那么就会直接启动一个核心线程来执行任务
如果线程池中的线程数量已经到达或者超过核心线程数量,那么任务会被插入到阻塞队列中等待
如果无法插入到阻塞队列中,则代表阻塞队列已经满了,如果线程数量未达到线程池的最大值,那么就会启动非核心线程来处理
如果线程池中线程的数量已经到达最大线程数量了,那么线程池就会拒绝该任务
这里我就举了一个运用线程池的例子
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,4,100, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
for(int i = 0;i<100;i++) {
Log.d("TAG", "" + i+":");
}
}
});
threadPoolExecutor.shutdown();
threadPoolExecutor.shutdownNow();
我规定我的corePoolSize为3,maximumPoolSize为4,没有设置allowCoreThreadTimeout为true。
所以100就为非corePool的线程的闲置时间为100,并且后面指定了停滞时间的单位为s,并规定了阻塞队列为ArrayBlockingQueue类型的有界队列,长度为10
然后调用execute方法执行0-99的输出
输出结束后设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程,设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
这是一种定长的线程池,它的主要特点就是它的corePoolSize等于maximumPoolSize且keepAliveTime为0使用的是**LinkedBlockingQueue< Runnable >()**作为阻塞队列(因为该阻塞队列没有指定大小,所以它是一个无界队列)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
它的特点就是
1.当线程处于空闲状态的时候,没有任何线程会被回收
这句话怎么理解呢?
因为它的线程全部都是核心线程,且没有设置allowCoreThreadTimeOut为true则代表无法回收它的线程
2.能更快地相应外界地请求,且没有超时机制
3.它的阻塞队列是一个无界队列
这就代表着无论多大的数据都能放进它的阻塞队列中
4.如果这时候线程已经满了,那么新的线程只能处于等待状态,等到其中一个线程空闲下来才能开始执行操作
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(20);
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
for(int i = 0;i<100;i++){
Log.d("TAG",""+i);
}
}
});
fixedThreadPool.shutdown();
fixedThreadPool.shutdownNow();
这里我设置让它的核心数量为20,打印0-99
相较于FixedThreadPool,这个线程池有许多不同
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
1.核心线程数为0,且最大线程数为无穷,且非CorePoolSize的keepAliveTime为60s
就代表如果它其中一个线程的闲置时间如果超过了60s就会被回收
2.如果一个这时候所有的线程都处于活动状态,那么该线程池就会创建新的线程来执行相关操作,否则就会利用空闲线程来处理新任务
3.SynchronousQueue与LinkedBlockingQueue不一样,它的任务队列相当于一个空集合,这就导致任何任务队列都会立即执行,在着这种情况下,该任务队列/阻塞队列是无法插入任务的,可以把它理解成一个无法存储元素的队列。
这就刚好和CacheThreadPool的设计理念不谋而论了,CacheThreadPool想做的就是只要你有新的任务需要执行,那么我就立刻给你一个线程让你执行
ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
cacheThreadPool.execute(new Runnable() {
@Override
public void run() {
}
});
cacheThreadPool.shutdown();
cacheThreadPool.shutdownNow();
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
这种其实算代理模式,我们直接看ScheduledThreadPoolExecutor中的代码就行了
1.我们发现它是有CorePoolSize的,且它的maximumPoolSize为无穷,它的keepAliveTime为10ms,可以说是相当短了
2.它的阻塞队列是DelayedWorkQueue,它是一个支持延时获取元素的无界阻塞队列。元素只有在其延迟时间到达时才能被取出。DelayedWorkQueue
内部使用了一个优先级队列(PriorityQueue
)来存储元素,并根据元素的延迟时间进行排序。
3.这种线程池主要用于执行特定的定时任务与具有固定周期的重复任务
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
}
},200,TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
}
},10,1000,TimeUnit.SECONDS);
scheduledThreadPool.shutdown();
scheduledThreadPool.shutdownNow();
它的操作与前面两个都不太一样,
前面还一样都是确定corePoolSize,但是后面是调用schedule方法,延迟多少时间后进行
如果是scheduleAtFixedRate,那么就是先延迟多少时间后进行,然后再按照后面给的那个参数每隔多长时间将这个任务重新执行一次
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
1.它的CorePoolSize与最大线程数量一样,都是1,这一点和FixedThreadPool很相似
2.它的keepAliveTime为0,且它的阻塞队列为LinkedBlockingQueue< Runnable >
我们再看看FixedThreadPool的源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以说SingleThreadExector就是特殊的FixedThreadPool
SingleThreadExector确保了所有的任务都在同一个线程中按顺序执行,统一外界任务到一个线程中使得任务之间不需要处理线程同步的问题
这个就没必要再说它的操作了
名称 | CorePoolSize | maximumPoolSize | keepAliveTime | workQueue | 特性 |
---|---|---|---|---|---|
FixedThreadPool | n | n | 0 | LinkedBlockingQueue | 所有任务全部都是CorePool处理,队列为无界队列,能更快地响应外界地请求,且没有超时机制,**如果所有的线程都在活动,那么新的消息只能放在LinkedBlockingQueue进行存储 ** |
CacheThreadPool | 0 | 无穷 | 60s | SynchronousQueue | 阻塞队列基本不存储信息。如果此时所有的线程都在活动,那么处理新消息就会新开一个线程进行处理,如果一个线程超过或等于60s仍然被闲置,那么就会被回收,所以它适合处理一些大量的,耗时较少的任务 |
ScheduleThreadPool | n | 无穷 | 自己定 | DelayedWorkQueue | 可以让它延迟时间执行,或者延迟时间后重复执行 |
SingleThreadPool | 1 | 1 | 0 | LinkedBlockingQueue | 不需要处理线程同步 |