文章目录
一、为什么要使用线程池
1、利用已有线程的潜力处理任务,减少上下文切换的成本
2、线程池创建一批线程,让线程重复的利用,减少内存的占用,可以减少线程数量,避免频繁的发生上下文切换。
阻塞队列(BlockingQueue):平衡生产者线程和消费者线程的桥梁
二、Java中的线程池
1、线程池状态
ThreadPoolExecutor(线程池)使用int高3为表示线程池状态,低29位表示线程数量。
上诉状态高三位数字大小比较:TERMINATED>TIDYING>STOP>SHUTDOWN>RUNNING(第一位为是符号位,代表负数)。
为什么不用两个整数,一个用来存储线程池状态,一个用来存储线程池数量?
因为把信息存储再一个原子变量中,目的是将线程池状态与线程个数合二为一,这样就可以只用一次cas操作,不用进行两次可以减少开销。
2、ThreadPoolExecutor
corePoolSize 核心线程数目 (最多保留的线程数)
maximumPoolSize 最大线程数目(最大线程数目等于核心线程数目加上救急线程数目)
keepAliveTime 生存时间 - (救急线程生存时间)
unit 时间单位 - (救急线程时间单位)
workQueue 阻塞队列
threadFactory 线程工厂(线程被创建时,为线程起名字)
3、Handler(拒绝策略)
从右到左api:抛出异常、让调用者自己处理、放弃本次任务、放弃最早排队的任务当前任务取而代之。
4、救急线程
线程池一开始是没有现成的,当任务来时线程池中的线程才会被创建(属于懒惰式加载)
如上图所示:线程池工作流程,当任务来时首先看核心线程数是否足够,如果核心线程数足够,就把任务交给线程池中的核心线程来处理,当核心线程数用完时,如果队列选择的是有界队列,新来的任务被放入阻塞队列中阻塞。当任务又来时,阻塞队列满了,线程池就会看看有没有救急线程,如果有救急线程就把新来的任务交给线程池中的救急线程来处理。当核心线程被占用,阻塞队列满,救急线程也用完,就会执行拒绝策略。
注意:如果阻塞队列选择的是有界队列才会有救急线程,如果选择的是无界队列,不会有救急线程,只有核心线程。
无界队列和有界队列的区别:无界队列可以放任意数量的任务,有界队列放的任务量有限。
关于救急线程
救急线程是有生存时间的,任务执行完毕,救急线程时间到了,救济线程就无了,等下次高峰期再被重新创建。而核心线程被创建后就会一直存在,任务执行完毕后也不会消失,这是核心线程和救急线程最大的区别。
三、Executor
根据ThreadPoolExecutor(线程构造方法),JDK为了更加方便,在JDK Executors类(线程工厂类)中提供了许多工厂方法用来创建各种用途的线程池。
1、newFixedThreadPool
特点核心线程数==最大线程数,没有救急线程,因此不需要超时时间
阻塞队列(LinkedBlockingQueue())无界,可以放任意数量的任务
适用于任务量已知,可以放任意数量的任务。
2、newCachedThreadPool
特点是核心线程为0;全部都是救急线程,且救急线程没有上限,可以无限的创建,救急线程生存时间是60s。
且阻塞队列使用的是SynochronousQueue(),没有容量,往队列是放元素放不进去,只用当其他线程来取的时候,才能把任务放进去。也就是说,假设t线程执行到放元素进队列时就阻塞住了,无法向下运行,只有其他线程把元素给取走了,t线程才能往下执行。
3、newSingleThreadExecutor
只有一个核心线程,任务数多余1时,会被放入无界队列排队。且任务执行完毕,唯一的线程也不会被释放。
单线程线程池的特点:当一个线程执行任务时,发生了异常或执行任务失败,此线程失效时,就会创建一个新的线程来顶替它,继续执行任务,但是单线程线程池永远只有一个线程,只有线程失效才会创建新的线程来顶替。
newSingleThreadExecutor用了一个包装类封装返回值,应用装饰器模式,只对外暴露了ExecutorService,因此不能调用调用newSingleThreadExecutor方法
Executor.newFixedThreadPool(1),为1时也能实现newSingleThreadExecutor类似的作用,即线程被销毁后仍能新创建一个,但是暴露了ThreadPoolExecutor下所示,可以调用其内部的方法。