深入浅出java线程池执行过程

引言

在Java并发编程中,线程池是一种被广泛应用的资源管理工具,它能够有效地管理和控制程序中的线程数量,从而提高系统性能、减少系统资源消耗。本文将深入剖析Java线程池的工作机制与执行过程。

一、线程池的基本概念

Java中的线程池主要由java.util.concurrent ThreadPoolExecutor类实现,其核心组件包括:工作队列(如ArrayBlockingQueue)、线程池(包含核心线程和非核心线程)以及拒绝策略等。线程池的主要任务是接收外部提交的任务并调度线程进行执行。

二、线程池的工作流程

直接上图:

线程池执行过程主要分为四个阶段

任务提交阶段

当我们调用ThreadPoolExecutor.execute(Runnable task)方法向线程池提交一个任务时,线程池内部会经历以下步骤:

  1. 判断当前线程池中的线程数量是否小于核心线程数:如果是,线程池会创建一个新的工作线程来执行这个任务,并将其初始化,然后调用线程的start()方法启动它。
  2. 若当前线程数不小于核心线程数,线程池会尝试将任务添加到工作队列(如ArrayBlockingQueue)。这里的"尝试"意味着如果队列已满,那么就会触发下一种处理方式。
  3. 如果工作队列已满,线程池会检查当前线程数是否小于最大线程数。如果小于最大线程数,线程池会创建一个新的线程来处理这个任务;否则,就需要采取拒绝策略来处理无法接受的新任务。

任务获取与执行阶段

每个工作线程都在一个无限循环中运行,它们会不断从工作队列中获取任务并执行。获取任务的过程通常涉及阻塞等待(如使用BlockingQueue的take()方法)。一旦获取到任务,线程就会执行Runnable.run()方法,完成任务的实际工作。

线程扩展与收缩

  • 线程扩展 :当线程池需要创建新线程来处理任务时,会调用ThreadFactory.newThread(Runnable r)方法创建新线程。默认的线程工厂会为每个线程赋予有意义的名字,比如"pool-1-thread-1"。
  • 线程收缩:线程池中的非核心线程在完成任务后不会立即销毁,而是进入保持存活状态。只有当这些线程在指定时间内(keepAliveTime参数指定)没有接收到新的任务,并且线程池中的线程数大于核心线程数时,这些空闲线程才会被终止。

拒绝策略

四种常见的拒绝策略如下:

  • AbortPolicy:默认拒绝策略,直接抛出RejectedExecutionException异常。
  • CallerRunsPolicy:调用者所在线程负责执行任务,这将降低新任务的提交速度。
  • DiscardPolicy:默默地丢弃任务,不执行也不抛出异常。
  • DiscardOldestPolicy:移除工作队列中最旧的任务(最先入队但尚未被执行的任务),然后重新尝试提交当前任务。

线程池核心参数

Java线程池的核心类是java.util.concurrent.ThreadPoolExecutor,它提供了丰富的参数用于定制线程池的行为。以下是ThreadPoolExecutor的主要构造函数参数及其含义:

  1. corePoolSize: 这是线程池的基本大小,即线程池即使在空闲时也会维持的最小线程数量。只要有任务提交过来,就会优先创建至corePoolSize个线程来处理任务。
  2. maximumPoolSize: 这是线程池能容纳的最大线程数。当线程池中的所有线程都处于活动状态且工作队列已满时,线程池会尝试增加更多的线程来处理任务,直到达到这个上限。
  3. keepAliveTime: 当线程池中线程数量超过corePoolSize时,多余的空闲线程在多长时间内(无新任务提交)仍不被使用就会被终止。这个时间长度是以TimeUnit指定的时间单位来衡量的。
  4. TimeUnit: keepAliveTime参数的时间单位,可以是纳秒(NANOSECONDS)、微秒(MICROSECONDS)、毫秒(MILLISECONDS)、秒(SECONDS)、分钟(MINUTES)、小时(HOURS)或天(DAYS)。
  5. BlockingQueue workQueue: 任务队列,用于存储等待执行的任务。可以选择不同的队列类型,例如无界队列(如LinkedBlockingQueue)、有界队列(如ArrayBlockingQueue)或其他满足BlockingQueue接口的自定义队列。
  6. ThreadFactory threadFactory: 用于创建新线程的工厂类,可以通过自定义ThreadFactory来设定新线程的名称、优先级和其他属性。
  7. RejectedExecutionHandler handler: 拒绝策略,当线程池和任务队列都无法处理新的任务时,会调用这个策略的rejectedExecution方法来处理被拒绝的任务。Java提供了一系列内置的拒绝策略如AbortPolicy、CallerRunsPolicy、DiscardPolicy和DiscardOldestPolicy,也可以自定义拒绝策略。

线程池实践

创建一个核心线程池数为2,最大线程数为3,阻塞队列大小为2的线程池。

下面的代码中,执行一次任务需要5000MS,而6个任务是一次性提交进去的,其中第四个任务就会因为 无法被核心线程执行,无法加入等待队列,无法创建新的非核心线程执行,而执行拒绝策略。

java 复制代码
public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executorService = new ThreadPoolExecutor(2,
                3,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(2));
​
        for (int i = 0; i < 6; i++) {
            executorService.execute(() -> {
                try {
                    Date date = new Date();
                    System.out.println("线程:" + Thread.currentThread().getName() + "报时:" + date);
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                }
​
            });
​
            System.out.println("等待队列中现在有" + executorService.getQueue().size() + "个任务");
            Thread.sleep(500);
        }
    }

运行结果如下:

从运行结果中,可以看出

1、前面两个任务是通过核心线程池来执行

2、第3、4个任务会被放到等待队列中

3、等待队列满了后,第五个任务是通过创建了一个非核心线程来执行任务,这是第三个线程

4、第六个任务由于核心线程池,等待队列,最大线程池数都已经达到最大,所以执行拒绝策略

5、从队列中获取任务,由于核心线程里面的任务已经执行完成,所以通过核心线程执行

相关推荐
LUCIAZZZ5 分钟前
高性能网络模式-Reactor和Preactor
java·服务器·开发语言·网络·操作系统·计算机系统
Dcs37 分钟前
Java 开发者必读:近期框架更新汇总(Spring gRPC、Micronaut、Quarkus 等)
java
Pi_Qiu_1 小时前
Python初学者笔记第十三期 -- (常用内置函数)
java·笔记·python
hsx6661 小时前
Android 基础筑基(一)
java
hy.z_7772 小时前
【数据结构】反射、枚举 和 lambda表达式
android·java·数据结构
從南走到北2 小时前
JAVA青企码协会模式系统源码支持微信公众号+微信小程序+H5+APP
java·微信·微信小程序·小程序·uni-app·微信公众平台
草履虫建模2 小时前
Ajax原理、用法与经典代码实例
java·前端·javascript·ajax·intellij-idea
强哥叨逼叨2 小时前
别被假象迷惑!揭秘 Java 线程池中“线程空着但任务卡着”的真相
java
_extraordinary_2 小时前
Java 栈和队列
java·开发语言
codervibe2 小时前
无微信依赖!纯网页扫码登录实现方案详解
java·后端