【后端面经-java】java线程池满的处理策略

Posted on 2023-06-21 09:16  CrazyPixel  阅读(539)  评论(1编辑  收藏  举报

1. 线程池介绍

1.1 基本作用

对多个线程使用的资源进行集中管理。

  • 降低资源消耗:
    • 复用线程,降低线程创建和销毁造成的消耗;
  • 线程资源管理
    • 提高管理效率;
  • 提高线程的响应速度
    • 在线程池中随时等待被执行,CPU不用等到线程创建时间;

1.2 处理流程

当一个线程进入线程池之后,会进行如下的处理步骤:

  • 首先查看核心线程池是否满

    • 如果没满,线程将在此处等待被调度执行;
  • 如果核心线程池满了,那么查看队列是否满了

    • 如果没满,线程在这里等待进入核心线程池;
  • 如果队列也满了,那么查看临时线程池是否满了

    • 如果没满,创建临时线程来处理任务。
  • 如果临时线程池也满了,那就要根据2.线程池满的处理策略进行线程处理。
    如下图所示:
    在这里插入图片描述

当调度者需要调度一个线程的时候,按照如下步骤:

  • 核心线程池中获取一个线程,执行任务;
  • 如果线程处于等待态,获取下一个线程继续执行;
  • 某一个任务执行完毕后,线程返回就绪态而不是终止态,放入线程池中复用。

1.3 线程池大小设置

  • CPU操作密集的任务

    • 由于线程操作多半需要占据CPU资源,因此一个线程运行的过程中基本上很少会出现某一线程进入等待态而调度下一个线程的情况;
    • 因此CPU调度线程的速度偏慢,因此线程池大小不应过大,一般为CPU核心数+1;这样可以保证CPU的效率最高;
    • 如果线程池容量过大,那么不仅对CPU运行是一个很大的负担,而且大量线程都处于等待运行的阶段,等待时间过长,可能出现响应过慢的情况。
  • I/O操作密集的任务

    • 对于I/O操作密集的任务,线程对于CPU的资源占用常常被I/O等操作打断,此时线程进入等待态,CPU继续调度下一个线程;
    • CPU调度线程的速度偏快,线程池大小可以尽量大一点,这样能够保障CPU资源的利用率,提高线程执行效率;
    • 如果线程池容量过小,CPU在调度一段时间之后,所有线程都进入等待态,此时就会出现CPU空等的情况,不利于资源有效利用。
  • 注意

  • 对于就绪态等待态等线程状态和生命周期的介绍,可参考这篇博客

1.4 线程池参数

线程池的构造方法如下所示:

public ThreadPoolExecutor(int corePoolSize,
                        int maximumPoolSize,
                        long keepAliveTime,
                        TimeUnit unit,
                        BlockingQueue<Runnable> workQueue,
                        ThreadFactory threadFactory,
                        RejectedExecutionHandler handler);

线程池的创建包含以下参数:

  • corePoolSize:核心线程池容量大小
    • 如前文所述,线程进行核心线程池即可等待调度执行
  • maximumPoolSize:最大线程池大小
    • 通过这个来判断线程池是否已满。
      MaximumPoolSize = CorePoolSize + WorkQueue + 临时线程池大小
      
  • workQueue:任务队列
    • 无法进入核心线程池的线程将进入任务队列等待进入池中;
    • 阻塞队列对象,一般需要设定容量大小
  • keepAliveTime:线程存活时间
    • 线程池已满的情况下,空闲多余的线程有个存活时间,超过这个时间还没有进入核心线程池,那么将被丢弃;
  • timeUnit:线程存活时间单位
    • 配合线程存活时间使用;
  • handler:拒绝策略
    • 当前线程池满了之后(超过maxmumPoolSize),对于新的线程的处理策略,
    • 包括四种,在2.1 默认--拒绝策略handler有详细论述
  • threadFactory:线程工厂
    • 用于创建线程池中的线程。

2. 线程池满的处理策略

2.1 默认--拒绝策略handler

线程池满了之后,一般的处理方式是丢弃某一线程,并且抛出异常。
Handler有四种策略:

  • AbortPolicy:直接抛出异常RejectedExecutionException
  • DiscardPolicy:直接丢弃任务,但是不抛出异常( 默认)。
  • DiscardOldestPolicy:丢弃队列中最旧的任务,然后重新尝试执行任务;
  • CallerRunsPolicy:由调用线程处理该任务。

3. 参考资料

参考博客-1
参考博客-2
参考博客-3
参考博客-4