线程的创建与销毁需要依赖操作系统,其代价是比较高昂的,频繁地创建与销毁线程对系统性能影响较大。 出于线程管理的需要,线程池应运而生。线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。使用线程池的好处在于: - 降低资源消耗:线程池通常会维护一些线程(数量为 corePoolSize),这些线程被重复使用来执行不同的任务,任务完成后不会销毁。在待处理任务量很大的时候,通过对线程资源的复用,避免了线程的频繁创建与销毁,从而降低了系统资源消耗。 - 提高响应速度:由于线程池维护了一批 alive 状态的线程,当任务到达时,不需要再创建线程,而是直接由这些线程去执行任务,从而减少了任务的等待时间。 - 提高线程的可管理性:使用线程池可以对线程进行统一的分配,调优和监控。 # 一、java中提供的线程池 ## 1.1 Executor 在Java中,线程池是由Executor框架实现的,Executor是最顶层的接口定义,其子类和实现类包括:**ExecutorService,ScheduledExecutorService,ThreadPoolExecutor,ScheduledThreadPoolExecutor,ForkJoinPool**等。 1. **Executor**:Executor是一个接口,只定义了一个execute()方法(void execute(Runnable command);),只能提交Runnable形式的任务,不支持提交Callable带有返回值的任务。 2. **ExecutorService**:ExecutorService在Executor的基础上加入了线程池的生命周期管理,可以通过shutdown或者shutdownNow方法来关闭线程池,关于这两个方法后文有详细说明。ExecutorService支持提交Callable形式的任务,提交完Callable任务后拿到一个Future(代表一个异步任务执行的结果)。 3. **ThreadPoolExecutor**:是线程池中最核心的类,后面有详细说明。 4. **ScheduledThreadPoolExecutor**:ThreadPoolExecutor子类,它在ThreadPoolExecutor基础上加入了任务定时执行的功能。 ## 1.2 线程池的创建 Executors中提供了一系列静态方法创建线程池: - **newSingleThreadExecutor**:一个单线程的线程池。如果因异常结束,会再创建一个新的,保证按照提交顺序执行。 - **newFixedThreadPool**:创建固定大小的线程池。根据提交的任务逐个增加线程,直到最大值保持不变。如果因异常结束,会新创建一个线程补充。 newCachedThreadPool:创建一个可缓存的线程池。会根据任务自动新增或回收线程。 - **newScheduledThreadPool**:支持定时以及周期性执行任务的需求。 - **newWorkStealingPool**:JDK8新增,根据所需的并行层次来动态创建和关闭线程,通过使用多个队列减少竞争,底层使用ForkJoinPool来实现。优势在于可以充分利用多CPU,把一个任务拆分成多个“小任务”,放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。 - newCachedThreadPool:缓存线程池 ![[Snipaste_2023-02-28_11-48-27.png]] 如下图所示,这些线程池都大部分是由ThreadPoolExecutor构造而成 ![[Snipaste_2023-02-28_11-48-27 1.png]] ## 1.3 ThreadPoolExecutor ![[Snipaste_2023-02-28_13-09-43.png]] ### corePoolSize 线程池容量(初始化线程数),这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法(从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程)。当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。 ### maximumPoolSize 线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程。 ### keepAliveTime 表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。 ### unit 参数keepAliveTime的时间单位 ### workQueue 工作队列,一般选择有界队列防止内存溢出 1. **ArrayBlockingQueue**:基于数组结构的有界阻塞队列,按FIFO排序任务; 2. **LinkedBlockingQuene**:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene; 3. **SynchronousQuene**:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene; 4. **priorityBlockingQuene**:具有优先级的无界阻塞队列; ### threadFactory ### handler 表示当拒绝处理任务时的策略 1. **ThreadPoolExecutor.AbortPolicy**:丢弃任务并抛出RejectedExecutionException异常。 2. **ThreadPoolExecutor.DiscardPolicy**:也是丢弃任务,但是不抛出异常。 3. **ThreadPoolExecutor.DiscardOldestPolicy**:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) 4. **ThreadPoolExecutor.CallerRunsPolicy**:由调用线程处理该任务 ## 1.4 线程池的关闭 ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中: - **shutdown()**:不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务 - **shutdownNow()**:立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务 ## 1.5 线程池容量的动态调整 ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(), - **setCorePoolSize**:设置核心池大小 - **setMaximumPoolSize**:设置线程池最大能创建的线程数目大小 当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。