- 最新思考:
现在大型App都使用了线程池,包括业务自己设置的线程池,第三方库内部也有自己的线程池,协程,RxJava这些框架都有自己的线程池。网上一些教程说我们在创建线程池的时候CPU密集型的线程数设置为CPU核数+1,IO密集型的线程数设置为2倍的CPU核数+1。但实际情况我观测到很多主流的App的主进程中实际线程数量都是100-200多个,这样的线程池线程个数设计是否还有意义?
是否可以整个APP共享一个线程池?怎么限制和协调?
- Executors.newSingleThreadExecutor();
核心线程数和最大线程数都为 1 的线程池,简单理解这个线程池只有一个线程。适合执行顺序任务,任务一个个来,反正唯一的核心线程在运行。
- Executors.newFixedThreadPool(3);
这个线程池跟上一个线程池非常像,核心线程数和最大线程数都是用同一个数值,只不过上一个线程池是写死的 1,而这个线程池可以自定义这个数值。适合知道并发的情况下对线程池做出限制。
- Executors.newCachedThreadPool();
这个线程池没有核心线程数,也没有限制最大线程数,那么可以得出这个线程池里面的线程都是非核心线程,并且还规定了非核心线程的存活时间不能超过 60 秒。适合处理大量且执行时间小的任务
- Executors.newScheduledThreadPool(4);
核心线程数是固定的,但不限制最大线程数,非核心线程的闲置时间不能超过 10 毫秒。适合处理定时或者周期性任务。
参考
ThreadPoolExecutor复用流程的一点理解
Android 线程池全解析
线程池相关的面试题
1、为什么使用线程池,线程池有什么作用?
线程池,就是一个池子,存放指定数量的线程来执行任务,处理完任务的线程不进行回收,而是阻塞等待下一个任务,避免了频繁的创建和销毁线程,达到了线程的重用。
2、如何创建一个线程池?
最常用的,使用ThreadPoolExecutor实现类来创建线程池
3、如何关闭一个线程池?
ExecutorService提供了两种方法来关闭线程池,shutdown()和shutdownNow()
(1) shutdown:拒收新的任务,立马关闭正在执行的任务,可能会引起报错,需要异常捕获
(2) shutdownNow:拒收新的任务,等待任务执行完毕,要确保任务里不会有永久等待阻塞的逻辑,否则会导致线程关闭不了
不是马上关闭,要想等待线程池关闭,还需要调用waitFermination来阻塞等待
还有一些业务场景下,需要知道线程池中的任务是否全部执行完成,当我们关闭线程池之后,可以用isTerminated来判断所有的线程是否执行完成,千万不要用isShutdown,它只 是返回你是否调用过shutdown的结果
4、submit()和execute()方法的区别?
execute()方法在Executor()接口中,且是接口中唯一的方法
submit()方法在ExecutorService中,ExecutorService接口继承Executor 接口
execute()方法,开启线程执行池中的任务
submit()方法,也可以做到execute()的作用,它还可以返回执行结果,它的功能是提交指定的任务去执行并且返回Future对象(即执行的结果)
submit()和execute()的区别:
(1) 接收的参数不一样
(2) submit()方法有返回值Future,而execute()方法没有返回值
(3) submit()方法方便处理Exception异常,意思就是,你在task里会抛出checked或者unchecked exception, 而又希望外面的调用者能够感知这些exception并作出及时的处理, 用 submit,通过捕获Future.get抛出的异常
5、为什么不建议使用Executors创建线程,而使用ThreadPoolExecutor实现类来创建线程?
Executors中FixedThreadPool使用的是LinkedBlockingQueue队列,近乎于无界,队列大小默认为Integer.MAX_VALUE,几乎可以无限制的放任务到队列中,线程池中数量是固定的,当线程池中线程数量达到corePoolSize(这里特指FixedThreadPool),不会再创建新的线程,所有任务都会入队到workQueue中,线程从workQueue中获取任务,但这个队列几乎永远不会满,只要队列不满,就不会再去创建新的线程,maximumPoolSize和keepAliveTime就不起作用,此时,如果线程池中的线程处理任务的时间特别长,导致无法处理新的任务,队列中的任务就会不断的积压,这个过程,会导致机器的内存使用不停的飙升,极端情况下会导致JVM OOM,系统就挂了。
总结:Executors中FixedThreadPool指定使用无界队列LinkedBlockingQueue会导致内存溢出,所以,最好使用ThreadPoolExecutor自定义线程池
换一种问法:线程池中,无界队列导致的内存飙升问题,同上
6、线程池如何调优
(1)首先,根据不同的需求选择线程池,如果需要单线程顺序执行,使用SingleThreadExecutor,如果已知并发压力,使用FixedThreadPool,执行大量任务,每个任务时间特别短,可以使用CachedThreadPool,需要后台执行周期任务的,可以使用ScheduledThreadPool,可以延时启动和定时启动线程池,
(2)如何确认线程池的核心线程数,分CPU密集型和IO密集型,如果是CPU密集型或计算密集型,因为CPU的利用率高,核心线程数可设置为n(核数)+1,如果是IO密集型,CPU利用率不高,可多给几个线程数,来进行工作,核心线程数可设置为2n(核数)