文章目录
什么是线程池?
线程池(Thread Pool)是一种线程管理的机制,它可以重用已创建的线程,避免频繁地创建和销毁线程,提高系统的性能和资源利用率。
线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待空闲状态。如果有新的线程任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,线程池会创建一个新线程进行处理或者放入队列(工作队列)中等待。
线程池的优点
- 减少线程创建和销毁的开销:线程的创建和销毁都是比较昂贵的操作。使用线程池可以重用已经创建的线程,避免了频繁地创建和销毁线程,减少了系统开销。
- 控制并发线程数:线程池可以限制系统中并发执行的线程数量,避免线程过多导致系统资源不足或过载的问题。通过设置线程池的大小和配置参数,可以更好地控制系统的并发度。
- 提高任务执行的响应速度:线程池可以将任务分配给空闲线程立即执行,提高了任务执行的响应速度。相比于创建新线程来执行任务,线程池中的线程已经处于就绪状态,可以更快地开始执行任务。
- .统一管理和监控:线程池提供了一种集中管理和监控线程的机制。可以通过线程池的管理接口对线程池进行配置、动态调整大小、监控线程执行状态等。
线程池常用的类和接口
ExecutorService接口:进行线程池的操作访问;
Executors类:创建线程池的工具类;
ThreadPoolExecutor及其子类:封装线程池的核心参数和运行机制;
线程池的创建方式和常用方法
通过Executors 类提供了一些静态工厂方法来创建不同类型的线程池
1.FixedThreadPool:固定数目的线程池
ExecutorService executor = Executors.newFixedThreadPool(int nThreads);
使用场景: 适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。
2.CachedThreadPool:线程数根据任务动态调整的线程池
ExecutorService executor = Executors.newCachedThreadPool();
使用场景: 用于并发执行大量短期的小任务。
3.SingleThreadExecutor:单线程线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
使用场景: 适用于串行执行任务的场景,将任务按顺序执行。
4.ScheduledThreadPool:能实现定时、周期性任务的线程池
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
使用场景: 周期性执行任务,并且需要限制线程数量的需求场景。
通过ThreadPoolExecutor方式自定义创建线程池
虽然线程池可以通过Executors工具类的方式去创建,但是Executors工具类中提供的newFixedThreadPool()、newSingleThreadExecutor()、**newCachedThreadPool()**等创建线程池的方法,但都有其局限性,不够灵活,另外由于前面几种方法内部也是通过new ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险;所以,更推荐使用通过ThreadPoolExecutor方式自定义创建线程池。
ExecutorService executor = ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) ;
- corePoolSize:线程池核心线程数;
- maximumPoolSize:线程池最大线程数;
- keepAliveTime:非核心线程存活时间;
- TimeUnit:参数keepAliveTime的时间单位;
- BolckingQueue:阻塞工作队列;
- ThreadFactory:线程工厂;
- RejectedExecutionHandler:拒绝策略
线程池的常用方法
ExecutorService.submit(Runnable task): 提交一个可执行的任务,并返回一个表示任务结果的 Future对象。
ExecutorService.submit(Callable task): 提交一个有返回值的任务,并返回一个表示任务结果的 Future 对象。
ExecutorService.execute(Runnable task): 执行一个可执行的任务,没有返回值。
ExecutorService.shutdown(): 关闭线程池,等待已提交的任务执行完毕后关闭线程池。
ExecutorService.shutdownNow(): 立即关闭线程池,尝试中断正在执行的任务,并返回等待执行的任务列表。
ExecutorService.awaitTermination(long timeout, TimeUnit unit): 等待线程池的关闭,最多等待指定的时间。
线程池的执行流程
- 线程池接收到任务:当有任务需要执行时,通过线程池的 execute() 或 submit() 方法提交任务给线程池。
- 判断核心线程是否可用:线程池首先会检查是否有空闲的核心线程可用,即线程池中当前正在运行的线程数是否小于核心线程数。如果有空闲的核心线程,则执行步骤4;如果没有空闲的核心线程,则执行步骤 3。
- 创建新的核心线程或放入任务队列:如果当前线程池中的线程数小于最大线程数,则线程池会创建一个新的核心线程,并让它执行任务。如果当前线程池中的线程数已达到最大线程数,则将任务放入任务队列等待执行。
- 核心线程执行任务:空闲的核心线程会立即执行提交的任务,并在执行完任务后再次进入空闲状态,等待下一个任务的到来。
- 任务队列调度任务:如果没有空闲的核心线程,线程池会将任务放入任务队列中。任务队列根据特定的调度策略(如先进先出、优先级等)从队列中选择任务,并将其分配给可用的线程进行执行。
- 创建新的非核心线程或拒绝任务:如果任务队列已满,并且当前线程数小于最大线程数,则线程池会创建一个新的非核心线程,并让它执行任务。如果当前线程数已达到最大线程数,根据配置的拒绝策略来处理无法执行的任务,如抛出异常或丢弃任务。
- 任务执行完毕和线程回收:当任务执行完毕后,线程会返回线程池并处于空闲状态。根据线程池的配置,空闲的非核心线程可能会被回收,以控制线程池的大小和资源占用。
- 关闭线程池:当不再需要线程池时,可以调用线程池的 shutdown()方法来关闭线程池。它会停止接受新的任务,并等待已提交的任务执行完毕后关闭线程池。
线程池的状态
线程池的状态分为:
- RUNNING:运行状态,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。该状态的线程池会接收新任务,并处理工作队列中的任务。
调用线程池的shutdown()方法,可以切换到SHUTDOWN关闭状态;
调用线程池的shutdownNow()方法,可以切换到STOP停止状态; - SHUTDOWN:关闭状态,该状态的线程池不会接收新任务,但会处理工作队列中的任务;当工作队列为空时,并且线程池中执行的任务也为空时,线程池进入
TIDYING状态; - STOP:停止状态,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行 的任务;线程池中执行的任务为空,进入TIDYING状态;
- TIDYING:整理状态,该状态表明所有的任务已经运行终止,记录的任务数量为0;terminated()执行完毕,进入TERMINATED状态;
- TERMINATED: 终止状态,该状态表示线程池彻底关闭。
总结
- 线程池的状态分为:RUNNING , SHUTDOWN, STOP, TIDYING,
TERMINATED; - 创建线程池更推荐使用通过ThreadPoolExecutor方式自定义创建线程池;
- 关闭线程池:void shutdown(); 或 shutdownNow();