三个思考问题:
1.怎么设置线程池的容量大小?
2.线程池添加任务的流程?
3.怎么设置核心线程数的大小?
线程池中Executor、ExecutorService、Executors三者的区别
(1)Executors类提供工厂方法创建不同类型的线程池,比如:
newSingleThreadExecutor():创建一个只有一个线程的线程池。
newFixedThreadPool(int numOfThreads)来创建固定线程数的线程池。
newScheduleThreadPool(int corePoolSize):创建一个线程池,它可以安排在给定延迟或者定期地执行。
newCacheThreadPool()创建一个可缓存的线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程,若无可回收,则新建线程(默认60s收回,适用于执行很多短期异步任务的程序,长时间保持空闲的线程池不会使用任何资源)
(2)ExecutorService接口继承了Executor接口,是Executor的子接口。ExecutorService除了允许客户端提供一个任务,还提供了用来控制线程池的方法,比如 调用 shutDown() 方法终止线程池。
什么是线程池:juc提供了一个 java.util.concurrent.Executor接口实现用于创建线程池。
阿里手册中强制不允许用Executors创建线程池:
原因是:
a.创建FixedThreadPool和SingleThreadPool 允许的请求队列长度是Integer.MAX_VALUE,可能会造成大量堆积请求,造成OOM。
b.创建CacheThreadPool和ScheduleThreadPool 允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而OOM。
那么怎么创建线程池呢?通过ThreadPoolExecutor的方式,这样子的处理方法让写的同学更加明确线程池的运行的运行规则。规避资源耗尽的风险。
AbstractExecutorService 实现了ExecutorService接口,ThreadPoolExecutor 继承AbstractExecutorService
ThreadPoolExecutor 构造方法几个核心参数:
1.corePoolSize:线程池的常驻核心线程数。
2.maxinumPoolSize:线程池能够容纳的最大线程数,这个值必须大于1.
3.keepAliveTime:多余的空闲线程的存活时间,当空闲时间达到keepAliveTime的时候,多余空闲线程被销毁直到只剩下corePoolSize个线程为止。
4.unit:keepAliveTime的单位。
5.workQueue:任务队列表示尚未被执行的任务。
6.threadFactory:表示生成线程池中工作线程的工厂,一般用线程池默认的线程池工厂。
7.handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数时。
解答开头问题1:
线程池的大小由上面的参数2决定
解答开头问题3:
线程池核心线程数的大小由上面的参数1决定
解答开头问题2-线程池添加队列的过程:
1.线程池创建后,提交任务给线程池,线程池判断当前线程数是不是达到核心线程数,如果没有达到,则线程池创建新的线程。
2.如果CoreSize加到已满,会将加入的任务放到任务队列中,此时核心线程会通过take阻塞的形式从任务队列中获取任务执行。
3.如果任务队列已经满了,核心线程处理消耗不掉,则线程池会通过pool(time,unit)的方式创建临时线程,如果临时线程在指定的时间获取到队列中的任务进行执行,则该线程被释放。
4.如果临时线程一直增加,和核心线程之和达到设定的最大线程数,则继续放入队列的任务会被预设的拒绝策略进行处理。
四种拒绝策略:
1.丢弃:ThreadPoolExecutor.DiscardPolicy
2.替换最老的那个请求:ThreadPoolExecutor.DiscardOldestPolicy()
3.报错,抛出异常:ThreadPoolExecutor.AbortPolicy()
4.重试,自动重复调用execute方法:ThreadPoolExecutor.CallerRunsPolicy(),它直接在execute 方法的调用线程中运行被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。
那么应该有多少个线程?
Runtime.getRuntime().availableProcessors()其实有点粗暴,并没有考虑到程序是cpu密集型还是I/O密集型
正常来说,
线程数 = CPU 核心数 *( 1 - IO 阻塞系数)
线程数并不是越多越好,要考虑线程切换的开销。
(什么是cpu或i/o计算密集型?
一般来说程序一般是cpu和I/O操作交互执行的,一般I/O操作比cpu计算慢很多,这种场景一般称为I/O密集型,相对的就是cpu计算机密集型。)
I/O 密集 型程序和 CPU 密集型程序,计算最佳线程数的方法是不同的。
CPU 密集型程序:理论上“线程的数量 =CPU 核数”就是最合适的,不过在工 程上,线程的数量一般会设置为“CPU 核数 +1”,这样的话,当线程因为偶尔的内存页失效或 其他原因导致阻塞时,这个额外的线程可以顶上,从而保证 CPU 的利用率。
I/O 密集 型程序:
最佳线程数 =1 +(I/O 耗时 / CPU 耗时
不过上面这个公式是针对单核 CPU 的,至于多核 CPU,也很简单,只需要等比扩大就可以了, 计算公式如下:
最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]
参考:https://www.jianshu.com/p/f5ead62827d7