当前位置: 首页 > article >正文

Java线程池解读

Java 线程池是一个提供多线程管理和调度的工具,通常用来处理多个并发任务。线程池能够帮助有效管理线程的创建、调度、执行和销毁,避免频繁的线程创建和销毁,提高系统性能。

前言

Java 线程池是面试中的常客,面试官经常会问线程池的作用和线程池的创建销毁这些基础问题,本文就Java线程池的基本概念、工作原理、实际案例展开阐述。

一、基本概念

线程池本质上是一个管理线程的容器,它包含了多个线程,可以用来执行多个任务。线程池的核心思想是:复用已创建的线程,避免频繁的线程创建和销毁操作。

为什么要使用线程池

  • 性能稳定性强:通过复用线程,避免频繁创建和销毁线程带来的开销。
  • 提高资源管理效率:线程池管理线程的最大数量,防止线程过多而导致资源竞争和系统崩溃。
  • 配置灵活:线程池的参数可以根据实际需求调整,允许在不同的任务量和工作负载下调整线程池的大小。
  • 支持定时以及周期性任务执行:线程池可以方便地支持定时任务和周期性任务的执行,这对于需要定时执行任务的应用非常有用。

二、核心接口

在Java中,线程池的核心接口和类主要位于java.util.concurrent包中。线程池结构图如下:
在这里插入图片描述

1. Executor 接口

Executor 是线程池的最基本接口,它负责提交任务。定义了执行任务的基本方法。

public interface Executor {
    void execute(Runnable command); // 提交任务给线程池
}

2. ExecutorService 接口

ExecutorServiceExecutor 的子接口,增加了用于管理线程池的方法。常用的方法包括:

  • submit() 提交任务并返回一个 Future 对象,允许获取任务执行结果或取消任务。
  • invokeAll() 提交多个任务并返回一个 List,用于批量处理任务。
  • shutdown()shutdownNow() 用于关闭线程池。
public interface ExecutorService extends Executor {
    <T> Future<T> submit(Callable<T> task); // 提交带返回值的任务
    <T> Future<T> submit(Runnable task, T result); // 提交Runnable任务,并返回结果
    List<Runnable> shutdownNow(); // 强制关闭线程池
    void shutdown(); // 优雅地关闭线程池
}

3. ThreadPoolExecutor 类

ThreadPoolExecutorExecutorService 的核心实现类,提供了灵活配置线程池参数的方法。常用的构造函数如下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)
  • corePoolSize:核心线程数,线程池最小线程数。
  • maximumPoolSize:最大线程数,线程池最多允许的线程数。
  • keepAliveTime:当线程池的线程超过 corePoolSize 时,空闲线程的最大存活时间。
  • unitkeepAliveTime 的时间单位。
  • workQueue:任务队列,用于存储等待执行的任务。

常用线程池类

  • newFixedThreadPool(int nThreads):创建固定大小的线程池,nThreads 为线程数。
  • newCachedThreadPool():创建可缓存的线程池,能够根据需要创建线程,线程空闲时会被回收。
  • newSingleThreadExecutor():创建单线程池,始终只有一个工作线程。
  • newScheduledThreadPool(int corePoolSize):创建定时任务线程池,支持定时和周期性任务。

三、线程池创建关闭

1、使用 ExecutorService 创建和使用线程池

import java.util.concurrent.*;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个线程池,最大线程数为 10,核心线程数为 5,任务队列长度为 100
        ExecutorService executorService = new ThreadPoolExecutor(
            5, 10, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100)
        );

        // 提交 10 个任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                System.out.println("Executing task " + taskId + " by " + Thread.currentThread().getName());
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

2、直接使用ThreadPoolExecutor类创建线程池

可以直接使用ThreadPoolExecutor类构造器来创建线程池,这样可以更灵活地设置参数,如核心线程数、最大线程数、工作队列、线程工厂、拒绝策略等。

int corePoolSize = 10;
int maximumPoolSize = 50;
long keepAliveTime = 120;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

ExecutorService threadPool = new ThreadPoolExecutor(
    corePoolSize,
    maximumPoolSize,
    keepAliveTime,
    unit,
    workQueue,
    threadFactory,
    handler
);

3、关闭线程池

  • shutdown():线程池会等到已经提交的任务执行完成后关闭,无法接受新的任务。
  • shutdownNow():线程池尝试停止所有正在执行的任务,并返回待执行任务的列表。

四、线程池工作原理

当一个任务被提交到线程池时,线程池根据其当前的状态决定如何处理任务:

  1. 如果当前工作线程数小于 corePoolSize,线程池会创建一个新的线程来处理任务。
  2. 如果当前工作线程数等于或大于 corePoolSize且队列尚未满,线程池将任务添加到队列中。
  3. 如果当前工作线程数大于 corePoolSize 且队列已满,线程池会创建新的线程,直到达到 maximumPoolSize
  4. 如果所有线程都在工作且队列已满,新的任务将被拒绝,具体的拒绝策略取决于 RejectedExecutionHandler的实现(默认是抛出异常)。

线程池任务处理流程图如下:
在这里插入图片描述

五、线程池异常处理

1. ThreadPoolExecutor 的异常处理机制
在使用 ThreadPoolExecutor 提交任务时,任务执行过程中的异常处理取决于几个因素,尤其是任务的类型(RunnableCallable)以及异常的传播方式。

  • Runnable 接口中的异常
    Runnable 是不返回结果的任务接口,它的 run() 方法不能抛出任何异常。因此,如果在 run() 方法中发生了异常,默认情况下,异常会被吞掉,线程池不会进行任何处理。
    默认行为
    线程池不会捕获 Runnable 中的异常。异常会在执行该任务的线程中被丢弃,不会传播到线程池外部。任务失败的信息不会返回给调用者。如果想要捕获并处理 Runnable 中的异常,你需要在 run() 方法内部手动捕获异常,并进行相应的处理。
Runnable task = () -> {
    try {
        // 任务代码
        throw new RuntimeException("Error in task");
    } catch (Exception e) {
        System.out.println("Exception caught: " + e.getMessage());
        // 处理异常,如记录日志
    }
};
executor.execute(task);
  • Callable 接口中的异常
    Callable 是带有返回值的任务接口,它的 call() 方法允许抛出异常。如果任务中的 call() 方法抛出异常,线程池会将异常封装到 Future 对象中,调用者可以通过 Future.get() 方法获取异常。
    处理 Callable 中的异常
    如果 call() 方法抛出异常,Future.get() 会抛出 ExecutionException,并且原始异常会作为 ExecutionExceptioncause
    可以通过 Future.get() 捕获并处理异常。
Callable<String> task = () -> {
    if (true) {
        throw new RuntimeException("Error in callable task");
    }
    return "Task Completed";
};

ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> future = executorService.submit(task);

try {
    // 获取结果,如果有异常会抛出 ExecutionException
    String result = future.get();
    System.out.println(result);
} catch (ExecutionException e) {
    System.out.println("Task failed with exception: " + e.getCause());
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 恢复中断状态
}

2. 线程池的异常处理机制:RejectedExecutionHandler
当线程池的工作队列已满,或线程池的线程数已经达到最大值时,任务会被拒绝执行。这时,线程池使用 RejectedExecutionHandler 来处理拒绝的任务。

RejectedExecutionHandler 是一个接口,它有四个常用实现:

  • AbortPolicy:默认策略,抛出 RejectedExecutionException
  • CallerRunsPolicy:由提交任务的线程来执行该任务。
  • DiscardPolicy:丢弃被拒绝的任务。
  • DiscardOldestPolicy:丢弃队列中最旧的任务,并执行当前任务。

如果线程池中的任务被拒绝执行,RejectedExecutionHandler 会被调用,允许我们自定义如何处理这些被拒绝的任务。可以在此处捕获和处理任务拒绝相关的异常。

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    1, 1, 0L, TimeUnit.MILLISECONDS, 
    new LinkedBlockingQueue<>(1),
    new ThreadPoolExecutor.DiscardOldestPolicy() // 自定义的拒绝策略
);

Runnable task = () -> {
    System.out.println("Executing task");
};

for (int i = 0; i < 5; i++) {
    executor.execute(task); // 会抛出被拒绝的任务
}

3. 自定义异常处理机制
可以通过 ThreadFactory 来为线程池中的每个线程提供自定义的异常处理机制。在 ThreadFactory 创建线程时,可以设置线程的 uncaughtExceptionHandler,这样可以捕获线程执行时未处理的异常。

ThreadFactory threadFactory = new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setUncaughtExceptionHandler((t, e) -> {
            System.out.println("Thread " + t.getName() + " failed with exception: " + e.getMessage());
        });
        return thread;
    }
};

ExecutorService executorService = new ThreadPoolExecutor(
    1, 1, 0L, TimeUnit.MILLISECONDS, 
    new LinkedBlockingQueue<>(1),
    threadFactory
);
executorService.submit(() -> {
    throw new RuntimeException("Exception in thread");
});

六、线程池应用场景

1、固定大小线程池

固定线程池FixedThreadPool)是线程池的一种实现方式,它通过固定数量的线程来执行提交的任务。在这种线程池中,线程的数量是固定的,线程池的大小在创建时被设定好,且不会发生变化。即使有大量的任务提交,线程池也只会使用有限数量的线程去处理任务,超出线程池容量的任务将被放入等待队列,直到线程池中的某个线程完成任务并空闲出来,才能继续执行新的任务。适用于任务量比较稳定的场景,可以高效管理线程资源。示意图如下:

在这里插入图片描述
使用 FixedThreadPool 创建线程池

import java.util.concurrent.*;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池,线程数为 3
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 提交 5 个任务
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                try {
                    System.out.println("Task " + taskId + " is being executed by " + Thread.currentThread().getName());
                    // 模拟任务执行
                    Thread.sleep(1000);
                    System.out.println("Task " + taskId + " completed by " + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

2、缓存型线程池

CachedThreadPool 是一种能够根据需求动态创建线程的线程池实现,其特点是当任务较少时,线程池的大小可以非常小,甚至为 0;当任务增多时,线程池可以根据需要动态创建新的线程来处理任务。线程池中的线程在任务完成后不会立即销毁,而是会被缓存一段时间。如果有新的任务提交,线程池会复用这些空闲的线程;如果任务长时间没有提交,空闲的线程会被销毁。适用于任务量不稳定的场景,可以根据任务需求动态增加线程数,线程空闲时会被回收。示意图如下:
在这里插入图片描述

使用 CachedThreadPool 创建线程池

ExecutorService executorService = Executors.newCachedThreadPool();

3、定时任务调度线程池

调度线程池可以设定一个周期,按照这个周期重复执行任务。适用于需要定时或周期性执行任务的场景。示意图如下:

在这里插入图片描述
创建 ScheduledThreadPool

import java.util.concurrent.*;

public class ScheduledThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个大小为 3 的调度线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);

        // 1. 延迟 2 秒执行一次任务
        scheduledExecutorService.schedule(() -> {
            System.out.println("Task with delay executed at " + System.currentTimeMillis());
        }, 2, TimeUnit.SECONDS);

        // 2. 每 3 秒执行一次任务
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("Periodic task executed at " + System.currentTimeMillis());
        }, 1, 3, TimeUnit.SECONDS);

        // 3. 每 3 秒执行一次任务,首次任务会在延迟 1 秒后执行
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            System.out.println("Periodic task with fixed delay executed at " + System.currentTimeMillis());
        }, 1, 3, TimeUnit.SECONDS);

        // 4. 等待一段时间后关闭线程池
        try {
            Thread.sleep(10000); // 等待 10 秒钟,让任务有时间执行
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        scheduledExecutorService.shutdown();
    }
}

六、注意事项

  • 线程池大小设置:根据系统的硬件资源(如CPU核心数)和任务的类型,适当调整线程池的大小。线程池过大会带来资源浪费,过小则可能导致任务执行延迟。
  • 合理选择队列:使用适当的队列类型(如 LinkedBlockingQueueArrayBlockingQueue)可以优化线程池性能。
  • 线程池监控与调试:在生产环境中,监控线程池的状态(如活跃线程数、等待任务数等)对于调优非常重要。

总结

Java 线程池是处理多线程并发任务的重要工具,能够有效管理线程的生命周期,提高性能和资源利用率。通过合理配置线程池参数和任务队列,可以根据业务需求优化线程池的性能。在实际开发中,需要根据任务的特点和系统的资源状况,选择合适的线程池类型和配置。


http://www.kler.cn/a/443427.html

相关文章:

  • [Git] git cherry-pick
  • 【CSS】设置滚动条样式
  • mysql中查询json的技巧
  • Bash语言的数据库编程
  • MCU 和 PSK
  • xss-labs关卡记录15-20关
  • 东方博宜24年12月-B组(才俊)- 重铠马的选择
  • 如何利用Python爬虫获得1688按关键字搜索商品
  • SSL Version 2 and 3 Protocol Detection漏洞修复
  • C#经典算法面试题
  • 【ORACLE】一个允许关键字作为别名所引起的语法歧义场景
  • Python轻量级NoSQL数据库TinyDB
  • 智能人家谱程序创意
  • 使用 Elasticsearch 查询和数据同步的实现方法
  • C语言中的转义字符
  • 答题考试系统v1.6.1高级版源码分享+uniapp+搭建测试环境
  • Java:链接redis报错:NoSuchElementException: Unable to validate object
  • SSM 赋能 Vue 助力:新锐台球厅管理系统的设计与实现的辉煌之路
  • 模型 六西格玛(质量管理)
  • 嵌入式单片机中对应GPIO外设详解实现
  • 图书馆管理系统(三)基于jquery、ajax
  • TypeScript 错误处理与调试
  • 计算机毕业设计论文指导
  • 游戏何如防抓包
  • (8)YOLOv6算法基本原理
  • Unity中的委托和事件(UnityAction、UnityEvent)