Java 线程池的工作原理及实践

什么是线程池?

线程池是一种用于管理和复用线程的机制

线程池的核心思想是预先创建一定数量的线程,并把它们保存在线程池中,当有任务需要执行时,线程池会从空闲线程中取出一个线程来执行该任务。任务执行完毕后,线程不是被销毁,而是返还给线程池,可以立即或稍后被再次用来执行其他任务。这种机制可以避免因频繁创建和销毁线程而带来的性能开销,同时也能控制同时运行的线程数量,从而提高系统的性能和资源利用率。

线程池的主要组成部分包括工作线程、任务队列、线程管理器等。线程池的设计有助于优化多线程程序的性能和资源利用,同时简化了线程的管理和复用的复杂性。

线程池有什么好处?

  • 减少线程创建和销毁的开销,线程的创建和销毁需要消耗系统资源,线程池通过复用线程,避免了对资源的频繁操作,从而提高系统性能;
  • 控制和优化系统资源利用,线程池通过控制线程的数量,可以尽可能地压榨机器性能,提高系统资源利用率;
  • 提高响应速度,线程池可以预先创建线程且通过多线程并发处理任务,提升任务的响应速度及系统的并发性能;

线程池状态

  • RUNNING:线程池一旦被创建,就处于RUNNING状态,任务数为0,能够接收新任务,对已排队的任务进行处理。
  • SHUTDOWN:不接收新任务,但能处理已排队的任务。当调用线程池的shutdown()方法时,线程池会由RUNNING转变为SHUTDOWN状态。
  • STOP:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。当调用线程池的shutdownNow()方法时,线程池会由RUNNINGSHUTDOWN转变为STOP状态。
  • TIDYING:当线程池在SHUTDOWN状态下,任务队列为空且执行中任务为空,或者线程池在STOP状态下,线程池中执行中任务为空时,线程池会变为TIDYING状态,会执行terminated()方法。这个方法在线程池中是空实现,可以重写该方法进行相应的处理。
  • TERMINATED:线程池彻底终止。线程池在TIDYING状态执行完terminated()方法后,就会由TIDYING转变为TERMINATED状态。

线程状态

  • 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  • 运行(RUNNABLE):Java线程中将就绪(READY)和运行中(RUNNING)两种状态笼统的称为"运行"。

a.就绪(READY):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中并分配cpu使用权 。

b.运行中(RUNNING):就绪(READY)的线程获得了cpu时间片,开始执行程序代码。

  • 阻塞(BLOCKED):表示线程阻塞于锁。
  • 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  • 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  • 终止(TERMINATED):表示该线程已经执行完毕。

拒绝策略

线程池的拒绝策略决定了在任务队列已满的情况下,如何处理新提交的任务。

  • AbortPolicy - 这是默认的拒绝策略,当线程池无法接受新任务时,会抛出RejectedExecutionException异常。这意味着新任务会被立即拒绝,不会加入到任务队列中,也不会执行。通常情况下都是使用这种拒绝策略。
  • DiscardPolicy - 这个策略在任务队列已满时,会丢弃新的任务而且不会抛出异常。新任务提交后会被默默地丢弃,不会有任何提示或执行。这个策略一般用于日志记录、统计等不是非常关键的任务。
  • DiscardOldestPolicy - 这个策略也会丢弃任务,但它会先尝试将任务队列中最早的任务删除,然后再尝试提交新任务。如果任务队列已满,且线程池中的线程都在工作,可能会导致一些任务被丢弃。这个策略对于一些实时性要求较高的场景比较合适。
  • CallerRunsPolicy - 这个策略将任务回退给调用线程,而不会抛出异常。调用线程会尝试执行任务。这个策略可以降低任务提交速度,适用于任务提交者能够承受任务执行的压力,但希望有一种缓冲机制的情况。

一般来说,默认的拒绝策略还是比较常用的,因为大多数情况下我们不太会让任务多到线程池中放不下,要不然就提升执行速度,要不然就提升队列长度了。

工作原理

  • 任务提交:当有新任务提交到线程池时,线程池会根据当前状态决定如何处理该任务。
  • 核心线程处理:如果当前运行的线程数少于核心线程数(corePoolSize),线程池会立即创建一个新线程来执行任务,即使其他核心线程处于空闲状态。
  • 任务队列缓冲:如果当前运行的线程数等于或大于核心线程数,新任务会被放入任务队列(workQueue)中等待执行。
  • 最大线程处理:如果任务队列已满且运行的线程数少于最大线程数(maximumPoolSize),线程池会创建新的线程来处理任务。
  • 拒绝策略执行:如果任务队列已满且运行的线程数等于最大线程数,线程池会执行拒绝策略(RejectedExecutionHandler)来处理新提交的任务。
  • 线程回收:当线程完成任务后,如果空闲时间超过keepAliveTime,非核心线程会被回收,以减少资源消耗。

如何使用

创建线程池

arduino 复制代码
public class ThreadPoolUtils {
    /**
     * 线程池
     */
    private static ExecutorService executor = initDefaultExecutor();

    /**
     * 统一的获取线程池对象方法
     */
    public static ExecutorService getExecutor() {
        return executor;
    }

    private static final int DEFAULT_THREAD_SIZE = 16;
    private static final int DEFAULT_QUEUE_SIZE = 10240;

    private static ExecutorService initDefaultExecutor() {
        return new ThreadPoolExecutor(
                DEFAULT_THREAD_SIZE,    // 核心线程数
                DEFAULT_THREAD_SIZE,    // 最大线程数
                300, TimeUnit.SECONDS,  // 线程空闲时间
                new ArrayBlockingQueue<>(DEFAULT_QUEUE_SIZE),   // 任务队列
                new DefaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()); // 拒绝策略
    }
}

创建一个简单的任务类,并将其提交到线程池中执行:

java 复制代码
class MyTask implements Runnable {
    private final int taskId;

    public MyTask(int taskId) {
        this.taskId = taskId;
    }

    @Override
    public void run() {
        System.out.println("Task " + taskId + " is being executed by thread " + Thread.currentThread().getName());
        try {
            // 模拟任务执行时间
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Task " + taskId + " has been completed.");
    }
}

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = ThreadPoolUtils.getExecutor();
        // 提交任务到线程池
        for (int i = 1; i <= 20; i++) {
            executor.submit(new MyTask(i));
        }

        // 关闭线程池
        executor.shutdown();

        try {
            // 等待所有任务执行完毕
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                // 如果等待超时,中断线程池中的线程
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            // 如果在等待过程中被中断,中断线程池中的线程
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
相关推荐
KubeSphere6 分钟前
全面升级!WizTelemetry 可观测平台 2.0 深度解析:打造云原生时代的智能可观测平台
后端
Frank_zhou6 分钟前
Tomcat - 启动过程:类加载机制详解
后端
bobz96511 分钟前
kubevirt 使用图和节点维护计算|存储|网络
后端
屠龙少年想成恶龙12 分钟前
jenkins的CICD部署
后端
满分观察网友z16 分钟前
递归与迭代的优雅之舞:我在评论区功能中悟出的“树”之道(104. 二叉树的最大深度)
后端·算法
加瓦点灯26 分钟前
面试官: 如何设计一个评论系统?
后端
前端付豪30 分钟前
22、前端工程化深度实践:构建、发布、CI/CD 流程重构指南
前端·javascript·架构
Ziegler Han30 分钟前
Java的Gradle项目,使用SLF4J+Log4j2+log4j2.xml
java·log4j·slf4j
郡杰38 分钟前
JavaWeb(4-Filter、Listener 和 Ajax)
后端
white camel42 分钟前
重学SpringMVC一SpringMVC概述、快速开发程序、请求与响应、Restful请求风格介绍
java·后端·spring·restful