1. 什么是线程池
线程池简单理解就是一个池子,里面存放着已经创建好的线程,当有任务提交到线程池里面来的时候,池子中的某个线程就会主动去执行该任务,当提交过来的任务比较多,池子中的线程数不够用时,会自动扩充新的线程到线程池子,最多扩充到配置的最大线程个数,而当任务比较少时,池子中的线程个数就自动回收,把线程资源释放掉;并且通常情况下,为了能缓存提交过来的未被处理的任务,就需要有一个任务队列来存放,这就是一个线程池。
2.为什么要有线程池?
我们知道创建和销毁一个对象是很费时间的,特别是一些比较耗费资源的对象的创建和销毁,比如创建数据库连接,创建网络连接,创建线程等,所以就出现“池化技术”,即复用已经创建的对象,那么这样做能够带来 3 个好处:
(1)降低资源消耗,通过复用已经创建的线程降低线程创建和销毁造成的系统资源消耗;
(2)提高性能,当执行大量异步任务时线程池能够提供更好的性能,在不使用线程池时,每
当需要执行异步任务时直接 new 一个线程来运行,而线程的创建和销毁是需要开销的,而线
程池里面的线程是可复用的,不需要每次执行异步任务时都重新创建和销毁线程,直接执行任
务即可;
(3)方便线程管理,线程是不能随随便滥用的,当不停地创建线程可能导致系统资源消耗殆
尽而崩溃,使用线程池可以限制创建的线程个数、动态新增线程数量等,提高了线程的可管理
性
3.Java 线程池之 Executor 框架
(1)为了实现线程池和管理线程池,JDK 给我们提供了基于 Executor 接口的一系列接口、抽象类、实现类,我们把它称作线程池的 Executor 框架,Executor 框架本质上是一个线程池;
(2)Java 线程(java.lang.Thread)被一对一映射为本地操作系统内核线程,Java 线程启动时会创建一个本地操作系统线程,操作系统会调度所有线程并将它们分配给可用的 CPU 执行,当该 Java 线程终止时,这个操作系统线程也会被回收;实际上这是两层线程调度模型:
①上层 Java 线程的调度由 Executor 框架调度;
②下层操作系统的线程调度由操作系统调度;
(3)Java 的线程是这么设计的,包含两部分:
①工作任务;(Runnable 和 Callable)
②执行机制;(Thread、Executor 框架)
3.1 Executor 框架的接口与类结构
java.util.concurrent (并发编程的工具) juc
java.util.concurrent.atomic (变量的线程安全的原子性操作)
java.util.concurrent.locks (用于锁定和条件等待同步等)
①Executor:顶级接口,提交任务
②ExecutorService:扩展了Executor接口,提供了submit等api
③ForkJoinPool:工作窃取,补充之前的线程池
④ThreadPoolExecutor:自定义线程池的时候使用
⑤java.util.concurrent.Executors: 有静态方法,可以创建线程池,返回线程池对象
3.2 线程池的 7 大参数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
(1)int corePoolSize, 线程池中的核心线程数量,即线程池中维护的最小的线程数量,即使这些线程处于空闲状态,它们也不会被销毁,除非设置了 allowCoreThreadTimeOut;默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程;在实际中如果需要线程池创建之后立即创建线程,可以通过以下两种方式:
①boolean prestartCoreThread(),初始化一个核心线程;
private ThreadPoolExecutor executor = new ThreadPoolExecutor(100,300,1000, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(50), new ThreadFactoryBuilder().setNamePrefix("Line-Control-").build(), new ThreadPoolExecutor.DiscardOldestPolicy()); boolean b = executor.prestartCoreThread();
②int prestartAllCoreThreads(),初始化所有核心线程;
(2)BlockingQueue<Runnable> workQueue
, 任务队列,当核心线程全部繁忙时,提交的 Runnable 任务存放到该任务队列中,等待被核心线程来执行
(3)int maximumPoolSize, 线程池中允许的最大线程数,当核心线程全部繁忙且任务队列存满之后,线程池会临时追加线程,直到总线程数达到 maximumPoolSize 这个上限;
(4)long keepAliveTime, 线程空闲超时时间,如果一个线程处于空闲状态,并且当前的线程数量大于 corePoolSize,
那么在指定时间后,这个空闲线程会被销毁;
(5)TimeUnit unit
keepAliveTime 的时间单位 (天、小时、分、秒…)
(6)ThreadFactory threadFactory, 线程工厂,用于创建线程,一般采用默认的Executors.defaultThreadFactory()即可,也可以自定义实现
(7)RejectedExecutionHandler handler, 拒绝策略(饱和策略),当任务太多来不及处理时,就需要进行任务拒绝。任务拒绝是线程池的保护措施,当核心线程 corePoolSize 正在执行任务、线程池的任务队列
workQueue 已满、并且线程池中的线程数达到 maximumPoolSize 时,就需要“拒绝”掉新提交
过来的任务;
JDK 提供了 4 种内置的拒绝策略:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy 和
DiscardPolicy;
①AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常,这是线程池默认
的拒绝策略,在任务不能再提交的时候抛出异常,让开发人员及时知道程序运行状态,这样能
在系统不能承载更大的并发量时,及时通过异常信息发现;
②DiscardPolicy:直接丢弃任务,不抛出异常,使用此策略可能会使我们无法发现系统的异
常状态,建议一些无关紧要的业务采用此策略;
③DiscardOldestPolicy:丢弃任务队列中靠最前的任务,并执行当前任务,是否要采用此拒绝
策略,根据实际业务是否允许丢弃老任务来评估和衡量;
④CallerRunsPolicy: 交由任务的调用线程(提交任务的线程,如main线程)来执行当前任务;这种拒绝策
略会让所有任务都能得到执行,适合大量计算类型的任务执行,使用这种策略的最终目标是要让每个任务都能执行完毕,而使用多线程执行计算任务只是作为增大吞吐量的手段;
除了上面的四种拒绝策略,还可以通过实现 RejectedExecutionHandler 接口,实现自定义的拒绝策略
3.3 ExecutorService接口常用方法
(1)void shutdown() 启动一次顺序关闭,执行以前提交的任务,不能再提交新的任务了。
(2)List<Runnable> shutdownNow()
停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
(3)<T> Future<T> submit(Callable<T> task)
执行带返回值的任务,返回一个Future对象。
(4)Future<?> submit(Runnable task) 执行 Runnable 任务,并返回一个表示该任务的 Future。
(5)<T> Future<T> submit(Runnable task, T result)
执行 Runnable 任务,并返回一个表示该任务的 Future。
4. 线程池状态
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
5.JDK内置线程池ExecutorService实现类
获取ExecutorService可以利用JDK中的Executors 类中的静态方法,常用获取方式如下:
5.1 newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
①核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
②阻塞队列是无界的,可以放任意数量的任务
③适用于任务量已知,相对耗时的任务
5.2 newCachedThreadPool
创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
5.3 newSingleThreadExecutor
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
(1)使用场景:希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
(2)自己创建的一个单线程和newSingleThreadExecutor区别
自己创建的一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施(任务队列中的其他任务将无法执行),而线程池还会新建一个线程,保证池的正常工作
(3)newFixedThreadPool(1)和newSingleThreadExecutor的区别
①Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改;FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
②Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改;对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
6. 线程池工作流程