深入解剖线程池(ThreadPoolExecutor)

多线程应用程序中,线程的创建和销毁开销相对较高。每次创建线程都需要操作系统分配资源,销毁线程也需要释放资源。线程池就是为了解决这个问题,如果某个线程执行完自己的“任务”之后,并不是将线程释放,而是放到一个“池子”中,下次如果需要用到线程继续执行任务的话直接从池子中取,这样就不用再去重复的创建销毁。

1 线程池(ThreadPoolExecutor)

Java中线程池的底层真正实现是通过ThreadPoolExecutor来实现的,在使用的时候提供了Executors类供开发者使用,并提供了一系列的功能线程池。所以先来了解ThreadPoolExecutor的底层代码,然后再来分析Executors。

通过追源码,ThreadPoolExecutor的构造方法如下:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
    
    
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

   
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
    
    
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

  
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
    
    
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

    
    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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

这是官方API文档的描述
在这里插入图片描述可以看到构造方法中有以下这些参数,参数有的是必需的,有的是可选的,现在理解一下这些参数:

必需参数

  • corePoolSize - 即使空闲时仍保留在池中的线程数(核心线程数),除非设置allowCoreThreadTimeOut
  • maximumPoolSize - 池中允许的最大线程数
  • keepAliveTime -当线程数大于核心时,多余的空闲线程在终止之前等待新任务的最大时间
  • unit - keepAliveTime参数的时间单位
  • workQueue - 在执行任务之前用于保存任务的队列。该队列将仅保存execute方法提交的Runnable任务,采用阻塞队列实现。(这里使用的阻塞式队列有有界和无界两种形式,对于有界阻塞队列来说,达到饱和的时候就会触发拒绝策略,但是当使用的是无界队列的时候就永远不可能“满”也就不会触发拒绝策略。)

可选参数

  • threadFactory:线程工厂。用于指定为线程池创建新线程的方式。
  • handler :拒绝策略。当达到最大线程数时需要执行的饱和策略。

当线程池的线程数达到最大线程数时,需要执行拒绝策略。
Executors 为我们实现了 4 种拒绝策略:

  1. AbortPolicy():超过负荷,直接抛出异常(默认策略).
  2. CallerRunsPolicy():调用者负责处理.
  3. DiscardOldestPolicy():丢弃队列中最早的任务,将新任务加入.
  4. DiscardPolicy():丢弃新来的任务.

简单描述一下线程池的工作原理,对上面的参数有具体的认识:
在这里插入图片描述代码示例

public class Demo1 {
    
    
    public static void main(String[] args) {
    
    
    	//创建线程池(传入参数)
        ExecutorService pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS,
                new SynchronousQueue<Runnable>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        //向线程池提交任务
        for (int i = 0; i < 3; i++) {
    
    
            pool.submit(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println("hello");
                }
            });
        }
    }
}

由于线程大于了线程池最大线程数,可以看到代码触发了拒绝策略,我们使用的是AbortPolicy(),所以超负荷就直接抛出异常。将循环中的3减少为2 就不会抛出异常。
在这里插入图片描述

2 线程池的使用(Executors)

Java对ThreadPoolExecutors进行了封装,方便使用者对线程池进行使用,Executors根据不同的使用场景提供了下面四种创建线程池的方式:

  • newFixedThreadPool: 创建固定线程数的线程池 定长线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池. 可缓存线程池
  • newSingleThreadExecutor: 创建只包含单个线程的线程池. 单线程化线程池
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令, 是进阶版的 Timer.定时线程池。

具体地

2.1 newFixedThreadPool

  • 源码:
    在这里插入图片描述在这里插入图片描述
  • 特点:
    固定大小的线程池
    包含核心线程和最大线程数都为n,即corePoolSize和maximumPoolSize均为n。
    阻塞队列没有大小限制。(这里对于keppalivetime参数为0的设置,我看见有很多博客将这里会对线程执行完任务后立马回收停止,我认为是不正确的,官方API对于这个参数的解释是
    在这里插入图片描述当线程数大于核心时,多余的空闲线程在终止之前等待新任务的最大时间,。默认情况下,线程池只会回收非核心线程,如果希望核心线程也要回收,可以设置allowCoreThreadTimeOut这个属性为true,一般情况下我们不会去回收核心线程。因为线程池本身就是实现线程的复用,而且这些核心线程在没有任务要处理的时候是处于阻塞状态并没有占用CPU资源。
  • 使用场景:适用于需要限制并发线程数量的场景,例如控制同时执行的线程数量,以避免资源耗尽。

2.2 newCachedThreadPool

  • 源码
    在这里插入图片描述

  • 特点:
    动态大小的线程池。
    没有核心线程,核心线程数为0,最大线程数为Integer.MAX_VALUE。
    使用SynchronousQueue作为阻塞队列,可无限扩展。
    非核心线程的存活时间为60秒,即空闲60秒后的线程将被终止。

  • 使用场景:
    适用于需要处理大量短时任务的情况,例如任务执行时间不定,需要动态分配线程。

2.3 newSingleThreadExecutor

  • 源码
    在这里插入图片描述

  • 特点:
    包含单一核心线程的线程池。
    使用LinkedBlockingQueue作为阻塞队列。

  • 使用场景:

    适用于需要顺序执行任务的情况,保证任务按照提交的顺序依次执行

2.4 newScheduledThreadPool

  • 源码
    在这里插入图片描述

  • 特点:

    固定大小的线程池,包含corePoolSize个核心线程。
    使用DelayedWorkQueue作为阻塞队列,支持延迟执行和定时周期性执行。

  • 使用场景:

    适用于需要按计划执行任务、延迟执行或周期性执行任务的情况,例如定时任务和调度任务

猜你喜欢

转载自blog.csdn.net/qq_45875349/article/details/133762646