正文
一、线程池优点
1、降低资源消耗:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统稳定性,通过重复利用已创建的线程可以降低线程的创建和销毁造成的消耗。
2、提高响应速度:任务到达时,无需等待线程创建即可立即执行。
3、提高线程的可管理性:线程池提供了一种限制、管理资源的策略,维护一些基本的线程统计信息,如已完成任务量等,通过线程池可以对线程资源进行资源统一分配、监控和调优。
二、线程池原理
创建线程池的方式
Executors.newCachedThreadPool(); 可缓存线程池
Executors.newFixedThreadPool();固定数量的线程池
Executors.newScheduledThreadPool() ; 定时执行的线程池
Executors.newSingleThreadExecutor(); 单线程线程池
线程池的创建有四种方式,但是底层都是使用了ThreadPoolExecutor构造函数。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
ThreadPoolExecutor参数说明
corePoolSize--------------核心线程数,必须大于等于0。
maximumPoolSize--------------最大线程数,必须大于核心线程数。
keepAliveTime---------------------非核心线程存活的时间,当非核心线程在空闲时间超过这个值之后,会关闭非核心线程。
TimeUnit------------------------------时间单位。纳秒,微秒,毫秒,秒,分,小时,天TimeUnit.NANOSECONDS、TimeUnit.MICROSECONDS、TimeUnit.MILLISECONDS、TimeUnit.SECONDS、TimeUnit.MINUTES、TimeUnit.HOURS、TimeUnit.DAYS
BlockingQueue<Runnable> workQueue--------------存放线程的阻塞队列。
RejectedExecutionHandler handler----------------------线程池拒绝策略的处理类
队列说明
1.ArrayBlockingQueue:有界队列,基于数组结构,按照队列FIFO原则对元素排序;
2.LinkedBlockingQueue:无界队列,基于链表结构,按照队列FIFO原则对元素排序,Executors.newFixedThreadPool()使用了这个队列; 无界默认是Integer.MAX_VALUE,有界则是 可以自己定义。
3.SynchronousQueue:同步队列,该队列不存储元素,每个插入操作必须等待另一个线程调用移除操作,否则插入操作会一直被阻塞,Executors.newCachedThreadPool()使用了这个队列;
4.PriorityBlockingQueue:优先级队列,具有优先级的无限阻塞队列。
以ArrayBlockingQueue为例如下
package com.xiaojie.juc.thread.pool; import java.util.concurrent.*; /** * @author xiaojie * @version 1.0 * @description: 缓存功能的线程池 * @date 2021/12/12 20:31 */ public class CachedThreadPoolDemo { public static void main(String[] args) { ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<String>(5, true); arrayBlockingQueue.offer("a"); arrayBlockingQueue.offer("b"); arrayBlockingQueue.offer("c"); arrayBlockingQueue.offer("d"); arrayBlockingQueue.offer("e"); System.out.println(arrayBlockingQueue.size()); System.out.println(arrayBlockingQueue.poll());//从队列中取值之后,删除数据 System.out.println(arrayBlockingQueue.size()); System.out.println(arrayBlockingQueue.peek());//取值后不删除数据 System.out.println(arrayBlockingQueue.size()); } }
线程池的执行流程
如果当前工作线程数小于核心线程数,执行器总是优先创建一个新的线程,而不是从线程队列中获取一个空闲线程。
如果线程池中的总任务数量大于核心线程数量,新接手的任务将会存入阻塞队列,一直到阻塞队列满为止。在核心线程已用完,而阻塞队列未满的情况下线程池不会创建新线程,而是复用核心线程。
当完成一个任务时,执行器优先从阻塞队列中获取下一个任务开始执行,一直到阻塞队列为空,其中所有的缓存任务被取光。
在核心线程已经用完并且阻塞队列也已经满了的情况下,如果线程池接收新的任务,将会为新任务创建一个新的线程(非核心线程),并且会立即执行新任务。
在核心线程用完,阻塞队列已满,一直会创建新的线程直到线程池中的线程总数超过最大线程数。如果超过最大线程数,线程池就会拒绝接收新任务,当新任务到来时,执行拒绝策略。
线程池拒绝策略
两种情况会拒绝处理任务:
1.当线程数已经达到maxPoolSize,并且队列已满,会拒绝新任务。
2.当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置,默认是AbortPolicy,会抛出异常。
ThreadPoolExecutor类有几个内部实现类来处理拒绝任务:
1.AbortPolicy 丢弃任务,抛运行时异常
2.CallerRunsPolicy 执行任务
3.DiscardPolicy 忽视,什么都不会发生
4.DiscardOldestPolicy 从队列中踢出最先进入队列的任务
5.实现RejectedExecutionHandler接口,可自定义处理器