线程池基础知识

作者:阿甘
链接:https://zhuanlan.zhihu.com/p/472592171
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

线程池基础知识

在Java语言中,虽然创建并启动一个线程非常方便,但是由于创建线程需要占用一定的操作系统资源,在高并发的情况下,频繁的创建和销毁线程会大量消耗CPU和内存资源,对程序性能造成很大的影响。为了避免这一问题,Java给我们提供了线程池。

线程池是一种基于池化技术思想来管理线程的工具。在线程池中维护了多个线程,由线程池统一的管理调配线程来执行任务。通过线程复用,减少了频繁创建和销毁线程的开销。

本章内容我们先来了解一下线程池的一些基础知识,学习如何使用线程池以及了解线程池的生命周期。

线程池的使用

线程池的使用和创建可以说非常的简单,这得益于JDK提供给我们良好封装的API。线程池的实现被封装到了ThreadPoolExecutor中,我们可以通过ThreadPoolExecutor的构造方法来实例化出一个线程池,代码如下:

// 实例化一个线程池  
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 10, 60,  
        TimeUnit.SECONDS, new ArrayBlockingQueue<>(20));  
// 使用线程池执行一个任务          
executor.execute(() -> {  
    // Do something  
});  
// 关闭线程池,会阻止新任务提交,但不影响已提交的任务  
executor.shutdown();  
// 关闭线程池,阻止新任务提交,并且中断当前正在运行的线程  
executor.showdownNow(); 

ThreadPoolExecutor的构造方法中参数众多,对于初学者而言在没有了解各个参数的作用的情况下很难去配置合适的线程池。因此Java还为我们提供了一个线程池工具类Executors来快捷的创建线程池。Executors提供了很多简便的创建线程池的方法,举两个例子,代码如下:

// 实例化一个单线程的线程池  
ExecutorService singleExecutor = Executors.newSingleThreadExecutor();  
// 创建固定线程个数的线程池 
ExecutorService fixedExecutor = Executors.newFixedThreadPool(10);  
// 创建一个可重用固定线程数的线程池  
ExecutorService executorService2 = Executors.newCachedThreadPool(); 

但是,通常来说在实际开发中并不推荐直接使用Executors来创建线程池,而是需要根据项目实际情况配置适合自己项目的线程池,关于如何配置合适的线程池这是后话,需要我们理解线程池的各个参数以及线程池的工作原理之后才能有答案。

线程池的生命周期

线程池从诞生到死亡,中间会经历RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED五个生命周期状态。

  • RUNNING 表示线程池处于运行状态,能够接受新提交的任务且能对已添加的任务进行处理。RUNNING状态是线程池的初始化状态,线程池一旦被创建就处于RUNNING状态。
  • SHUTDOWN 线程处于关闭状态,不接受新任务,但可以处理已添加的任务。RUNNING状态的线程池调用shutdown后会进入SHUTDOWN状态。
  • STOP 线程池处于停止状态,不接收任务,不处理已添加的任务,且会中断正在执行任务的线程。RUNNING状态的线程池调用了shutdownNow后会进入STOP状态。
  • TIDYING 当所有任务已终止,且任务数量为0时,线程池会进入TIDYING。当线程池处于SHUTDOWN状态时,阻塞队列中的任务被执行完了,且线程池中没有正在执行的任务了,状态会由SHUTDOWN变为TIDYING。当线程处于STOP状态时,线程池中没有正在执行的任务时则会由STOP变为TIDYING。
  • TERMINATED 线程终止状态。处于TIDYING状态的线程执行terminated()后进入TERMINATED状态。

根据上述线程池生命周期状态的描述,可以画出如下所示的线程池生命周期状态流程示意图。

 

 

线程池的工作机制

ThreadPoolExecutor中的参数

上一小节中,我们使用ThreadPoolExecutor的构造方法来创建了一个线程池。其实在ThreadPoolExecutor中有多个构造方法,但是最终都调用到了下边代码中的这一个构造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {  
    public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime, 
                               TimeUnit unit,  
                              BlockingQueue<Runnable> workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler) {  
        // ...省略校验相关代码  
        this.corePoolSize = corePoolSize;  
        this.maximumPoolSize = maximumPoolSize;  
        this.workQueue = workQueue;  
        this.keepAliveTime = unit.toNanos(keepAliveTime);  
        this.threadFactory = threadFactory;  
        this.handler = handler;  
    }  
    // ...     
} 

这个构造方法中有7个参数之多,我们逐个来看每个参数所代表的含义:

  • corePoolSize 表示线程池的核心线程数。当有任务提交到线程池时,如果线程池中的线程数小于corePoolSize,那么则直接创建新的线程来执行任务。
  • workQueue 任务队列,它是一个阻塞队列,用于存储来不及执行的任务的队列。当有任务提交到线程池的时候,如果线程池中的线程数大于等于corePoolSize,那么这个任务则会先被放到这个队列中,等待执行。
  • maximumPoolSize 表示线程池支持的最大线程数量。当一个任务提交到线程池时,线程池中的线程数大于corePoolSize,并且workQueue已满,那么则会创建新的线程执行任务,但是线程数要小于等于maximumPoolSize。
  • keepAliveTime 非核心线程空闲时保持存活的时间。非核心线程即workQueue满了之后,再提交任务时创建的线程,因为这些线程不是核心线程,所以它空闲时间超过keepAliveTime后则会被回收。
  • unit 非核心线程空闲时保持存活的时间的单位
  • threadFactory 创建线程的工厂,可以在这里统一处理创建线程的属性
  • handler 拒绝策略,当线程池中的线程达到maximumPoolSize线程数后且workQueue已满的情况下,再向线程池提交任务则执行对应的拒绝策略

线程池工作流程

线程池提交任务是从execute方法开始的,我们可以从execute方法来分析线程池的工作流程。

(1)当execute方法提交一个任务时,如果线程池中线程数小于corePoolSize,那么不管线程池中是否有空闲的线程,都会创建一个新的线程来执行任务。

 

 

(2)当execute方法提交一个任务时,线程池中的线程数已经达到了corePoolSize,且此时没有空闲的线程,那么则会将任务存储到workQueue中。

 

 

(3)如果execute提交任务时线程池中的线程数已经到达了corePoolSize,并且workQueue已满,那么则会创建新的线程来执行任务,但总线程数应该小于maximumPoolSize。

 

 

(4)如果线程池中的线程执行完了当前的任务,则会尝试从workQueue中取出第一个任务来执行。如果workQueue为空则会阻塞线程。

 

 

(5)如果execute提交任务时,线程池中的线程数达到了maximumPoolSize,且workQueue已满,此时会执行拒绝策略来拒绝接受任务。

 

 

(6)如果线程池中的线程数超过了corePoolSize,那么空闲时间超过keepAliveTime的线程会被销毁,但程池中线程个数会保持为corePoolSize。

 

 

(7)如果线程池存在空闲的线程,并且设置了allowCoreThreadTimeOut为true。那么空闲时间超过keepAliveTime的线程都会被销毁。

 

 

线程池的拒绝策略

如果线程池中的线程数达到了maximumPoolSize,并且workQueue队列存储满的情况下,线程池会执行对应的拒绝策略。在JDK中提供了RejectedExecutionHandler接口来执行拒绝操作。实现RejectedExecutionHandler的类有四个,对应了四种拒绝策略。分别如下:DiscardPolicy 当提交任务到线程池中被拒绝时,线程池会丢弃这个被拒绝的任务

  • DiscardOldestPolicy 当提交任务到线程池中被拒绝时,线程池会丢弃等待队列中最老的任务。
  • CallerRunsPolicy 当提交任务到线程池中被拒绝时,会在线程池当前正在运行的Thread线程中处理被拒绝额任务。即哪个线程提交的任务哪个线程去执行。
  • AbortPolicy 当提交任务到线程池中被拒绝时,直接抛出RejectedExecutionException异常。
posted @ 2022-03-31 14:22  甜菜波波  阅读(54)  评论(0编辑  收藏  举报