线程池笔记
前言:线程池相关的所有类都在java.util.concurrent包下面
一、线程池的继承关系:
二、重点介绍ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
xxx
}
1、线程池工作原理:
a、提交优先级:核心线程数 > 工作队列 > 非核心线程数
创建线程达到核心线程数后,再来任务会放入工作队列中,当工作队列也满了,再来任务会依据核心线程数的数量创建线程,当非核心线程数也满了就会执行拒绝策略
b、执行优先级:核心线程数 > 非核心线程数 > 工作队列
执行线程任务时,优先执行核心线程数的任务,其次非核心线程创建的任务,最后才是工作队列中等待的任务
2、重点解释一下ThreadPoolExecutor构造函数的几个参数,对理解线程池非常重要
(1)、int corePoolSize:核心线程数,也就是线程池创建后会一直保存几个线程
(2)、int maximumPoolSize:最大线程数,最大线程数 = 核心线程数 + 非核心线程数(没有这个参数,但在源码中存在的概念)
(3)、long keepAliveTime:非核心线程数存活的时间
(4)、TimeUnit unit:非核心线程数存活时间的单位,例如毫秒,秒等
(5)、BlockingQueue workQueue:工作队列
(6)、ThreadFactory threadFactory:创建线程的工厂,一般用默认
(7)、RejectedExecutionHandler handler:拒绝策略,当核心线程数满了,工作队列满了,非核心线程数也满了,此时再有业务调用线程池创建线程时的策略,比如策略可以设置抛异常,可以设置哪个线程调用线程池执行任务哪个线程处理,可以设置丢弃最后一次任务等。
三、常用创建线程池的方法,在这里涉及到一个设计模式工厂模式,通过调用Executors来创建线程池,而不是直接new线程池(值得学习):
1、Executors.newCachedThreadPool():这种方式创建的线程池我们进源码看下
核心线程数0:表示线程池创建时不创建任何线程
最大线程数Integer.MAX_VALUE即21亿多:表示非核心线程数趋近于无限
非核心线程数存活时间60:
非核心线程数存活时间单位秒:表示创建出来的线程60s内没有被使用,就自动销毁
工作队列SynchronousQueue同步队列:表示队列中只有一个位置,只会有一个任务在等待,其他任务会被创建线程池立马执行
缺点:由于创建线程数趋近于无限大,会造成cpu100%
2、Executors.newFixedThreadPool(5):这种方式创建的线程池我们进源码看下
核心线程数由外部传参决定:核心线程数和最大线程数一致
最大线程数由外部传参决定:核心线程数和最大线程数一致
非核心线程数存活时间0:
非核心线程数存活时间单位毫秒:表示创建出来的线程只要不被使用,就立马销毁
工作队列LinkedBlockingQueue链表队列:我们进LinkedBlockingQueue的构造方法看下
无参构造,默认是int类型的最大值21亿+
总结:newFixedThreadPool这个方式创建出来的线程,创建时传参是几就会创建几个线程,后来的任务会被放到一个21亿+的队列中等待,没有非核心线程的概念,也就没有非核心线程的存活时间(所以源码中存活时间是0)
缺点:如果任务一直源源不断到来,队列中将一直存储,队列占用内存,会导致内存溢出oom
3、Executors.newSingleThreadExecutor():这种方式创建的线程池我们进源码看下
核心线程数1:核心线程数和最大线程数固定始终是1
最大线程数1:核心线程数和最大线程数固定始终是1
非核心线程数存活时间0:
非核心线程数存活时间单位毫秒:表示创建出来的线程只要不被使用,就立马销毁
工作队列LinkedBlockingQueue链表队列:上面我们已经看过LinkedBlockingQueue源码中的构造了,即int类型最大值21亿+
总结:newSingleThreadExecutor这个方式创建出来的线程,源码如名single,所以源码中只会创建1个线程,自始至终都是这1个线程在执行任务,后续任务到来都会被放到一个21亿+的队列中等待,没有非核心线程的概念,也就没有非核心线程的存活时间(所以源码中存活时间是0)
缺点:如果任务一直源源不断到来,队列中将一直存储,队列占用内存,同样会导致内存溢出oom
4、Executors.newScheduledThreadPool不甚了解,待日后研究补充
四、谈谈对线程池使用时,具体应该使用哪种线程池和怎样设置参数的理解:
这个问题其实本人暂时并未有答案,个人感觉丝毫使用弹性很大,举几个例子:
1、在xxAPP中,线程池源码用的是Executors.newCachedThreadPool(),也就是如果也源源不断的任务通过线程池过来,就可以创建接近无数个线程(21亿+)同时工作,每个任务创建一个线程。这种方法从理论上分析会造成cpu100%,但实际该APP已经诞生n多年了,底层源码一直这样写,好像也没遇到什么问题。
2、在xxAPP中,线程池源码是这样写的new ThreadPoolExecutor(4, 4, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());通过上面我们的解释,这样写线程池始终只会创建4个线程,其他任务到来将一直在一个接近无限大(21亿+)的队列中等待。这种写法理论上会造成内存溢出oom,但实际项目运行也已n年,似乎也没遇到什么这方面的问题。
3、在xxAPP中,线程池源码是这样写的new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());这样写线程池和第2种类似。理论上会造成内存溢出oom,但实际项目运行也已n年,似乎也没遇到什么这方面的问题。
总结:这已经超出本人理解范围,通过这几个项目的实际源码来看,似乎线程池的各种使用并未有太大影响(当然不能极端就写成1个线程执行任务)。
就总结到这里吧,有问题再补充。以后要常写文章,常总结,加油自己!