博客
关于我
ThreadPoolExecutor-线程池知多少
阅读量:662 次
发布时间:2019-03-15

本文共 5061 字,大约阅读时间需要 16 分钟。

ThreadPoolExecutor:线程池的核心原理与应用实践

线程池作为Java中并发执行的核心工具之一,通过合理管理资源,显著提升了程序的效率和响应速度。本文将从线程池的设计目的、创建方式、构造参数、任务执行机制以及实际应用场景等方面,深入探讨ThreadPoolExecutor的工作原理和使用技巧。


一、线程池的创建目的

线程池的核心设计初衷在于解决资源分配和任务执行的性能瓶颈。传统的单线程模型在处理多任务时,需要频繁创建和销毁线程,这种做法不仅效率低下,还可能导致资源耗尽。线程池通过将线程资源池化,实现了任务的“即时”执行和“即进即出”管理模式。

线程池的主要优势体现在以下几个方面:

  • 资源优化:避免频繁创建和销毁线程,减少内存开销。
  • 任务高效处理:重复使用已有线程,减少任务处理时间。
  • 资源管理灵活:支持多种拒绝策略,保护资源不被过度占用。

  • 二、为什么选择ThreadPoolExecutor而不是 Executors

    ThreadPoolExecutor是线程池操作的核心接口,而 Executors框架提供了多种线程池实现方式,如FixedThreadPool、SingleThreadExecutor、CachedThreadPool等。虽然这些实现方式简化了线程池的使用,但直接使用ThreadPoolExecutor的方式更为灵活和安全。

    以下是选择ThreadPoolExecutor的主要原因:

  • 明确的线程池规则:通过ThreadPoolExecutor的方法构建线程池,开发者可以更直观地控制线程的数量和管理策略。
  • 资源可控性:避免因默认设置带来的潜在风险,例如任务队列溢出或线程资源耗尽。
  • 灵活的扩展性:ThreadPoolExecutor支持自定义拒绝策略和任务处理流程,适用于不同场景需求。

  • 三、ThreadPoolExecutor的构造与参数解析

    ThreadPoolExecutor的构造涉及多个关键参数,理解每个参数的含义和作用对线程池的正确使用至关重要。

  • 核心线程数(corePoolSize)

    • 表示线程池的常驻核心线程数,即使在没有任务的情况下也会保持这些线程。
    • 设置过小可能导致频繁创建和销毁线程,影响性能;过大则可能浪费资源。
  • 线程最大数量(maximumPoolSize)

    • 线程池的最大线程数,通常应大于核心线程数。
    • 当任务量大于核心线程数时,线程池会扩展到最大值。
  • 线程存活时间(keepAliveTime)

    • 控制线程池中多余线程的存活时间,防止线程资源耗尽。
    • 超时后,线程会销毁回到核心线程数。
  • 任务队列(workQueue)

    • 负责存储待处理任务,线程池的任务调度依赖于这个队列。
    • 队列的类型(如LinkedBlockingQueue)会影响线程池的性能表现。
  • 线程工厂(threadFactory)

    • 定义如何创建新线程。默认工厂通常使用默认的线程创建模式。
  • 拒绝策略(handler)

    • 定义线程池在资源耗尽时的任务处理方式。
    • 常见策略包括 AbortPolicy(抛出异常)、CallerRunsPolicy(将任务交给当前线程)、DiscardPolicy(忽略任务)等。

  • 四、ThreadPoolExecutor的核心执行逻辑

    ThreadPoolExecutor的执行过程可以分为以下几个阶段:

  • 任务提交:调用execute或submit方法提交任务。
  • 线程判断:根据当前线程数和任务队列状态决定是否需要创建新线程。
  • 线程启动:如果需要新线程,创建线程并运行任务。
  • 任务处理:线程执行任务,可能将结果返回(submit)或不返回(execute)。
  • 异常处理:如果任务执行过程中遇到异常,根据拒绝策略处理。

  • 五、任务执行与线程图展示

    在线程池执行任务时,线程的状态会发生变化,从而形成线程图。通过图示可以清晰地观察任务执行的流程和线程之间的关系。

    线程图示

    通过图示可以看到,线程池中的线程在接收任务后,会执行任务并完成后返回。线程的状态从“运行中”到“完成”,任务从队列中取出并逐一执行。


    六、execute与submit的区别

    execute和submit都是线程池执行任务的方法,但两者在功能上有明显差异:

  • execute
    • 不返回结果,适用于不需要返回值的任务。
    • 方法签名:void execute(Runnable r)
  • submit
    • 返回Future对象,可以获取任务执行结果。
    • 方法签名:<T> Future<T> submit(Runnable r)
  • 示例

    ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(20));// 使用execute提交任务executor.execute(() -> {    System.out.println("Hello, execute.");});// 使用submit获取执行结果Future
    future = executor.submit(() -> { System.out.println("Hello, submit."); return "Success";});System.out.println(future.get());

    七、线程池的拒绝策略

    线程池的拒绝策略决定了资源耗尽时任务的处理方式。默认策略是AbortPolicy,会抛出异常并终止任务执行。

    常见策略

  • AbortPolicy:直接抛出异常,任务无法继续执行。
  • CallerRunsPolicy:将任务交给当前线程执行,减少线程池的负担。
  • DiscardPolicy:忽略当前任务,不影响其他任务的执行。
  • DiscardOldestPolicy:忽略队列中最早的任务,优先处理新任务。
  • 示例

    ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 10, TimeUnit.SECONDS, new LinkedBlockingQueue(2), ThreadPoolExecutor.AbortPolicy());for (int i = 0; i < 6; i++) {    executor.execute(() -> {        System.out.println(Thread.currentThread().getName());    });}// 输出结果:// pool-1-thread-2// pool-1-thread-1// pool-1-thread-3// pool-1-thread-1// pool-1-thread-1// pool-1-thread-3// RejectedExecutionException: Task ... rejected from java.util.concurrent.ThreadPoolExecutor

    八、自定义拒绝策略

    为了满足特定场景需求,可以自定义拒绝策略。通过提供自定义的RejectedExecutionHandler,可以在资源耗尽时采取特定处理方式。

    示例

    ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 10, TimeUnit.SECONDS, new LinkedBlockingQueue(2), new RejectedExecutionHandler() {    @Override    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {        System.out.println("这是自定义的拒绝策略");    }});for (int i = 0; i < 15; i++) {    executor.execute(() -> {        System.out.println(Thread.currentThread().getName());    });}

    九、ThreadPoolExecutor的扩展与优化

    线程池可以通过扩展ThreadPoolExecutor类,添加自定义的执行前后处理逻辑,提升线程池的功能和性能。

    示例

    public class ThreadPoolExtend {    public static void main(String[] args) {        MyThreadPoolExecutor executor = new MyThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new LinkedBlockingQueue());        for (int i = 0; i < 3; i++) {            executor.execute(() -> {                System.out.println(Thread.currentThread().getName());            });        }    }    static class MyThreadPoolExecutor extends ThreadPoolExecutor {        private final ThreadLocal
    localTime = new ThreadLocal<>(); public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
    workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override protected void beforeExecute(Thread t, Runnable r) { Long sTime = System.nanoTime(); localTime.set(sTime); System.out.println(String.format("%s | before | time=%s", t.getName(), sTime)); super.beforeExecute(t, r); } @Override protected void afterExecute(Runnable r, Throwable t) { Long eTime = System.nanoTime(); Long totalTime = eTime - localTime.get(); System.out.println(String.format("%s | after | time=%s | 耗时:%s 毫秒", Thread.currentThread().getName(), eTime, (totalTime / 1000000.0))); super.afterExecute(r, t); } }}

    通过以上内容,可以清晰地了解ThreadPoolExecutor的工作原理及其在实际应用中的操作方法。线程池作为Java中并发编程的核心工具,其正确使用能够显著提升程序的性能和稳定性。在实际开发中,合理配置线程池参数并选择适当的拒绝策略,是确保程序高效运行的关键。

    转载地址:http://ahjmz.baihongyu.com/

    你可能感兴趣的文章
    Netpas:不一样的SD-WAN+ 保障网络通讯品质
    查看>>
    Netty WebSocket客户端
    查看>>
    Netty工作笔记0011---Channel应用案例2
    查看>>
    Netty工作笔记0014---Buffer类型化和只读
    查看>>
    Netty工作笔记0050---Netty核心模块1
    查看>>
    Netty工作笔记0084---通过自定义协议解决粘包拆包问题2
    查看>>
    Netty常见组件二
    查看>>
    netty底层源码探究:启动流程;EventLoop中的selector、线程、任务队列;监听处理accept、read事件流程;
    查看>>
    Netty核心模块组件
    查看>>
    Netty框架的服务端开发中创建EventLoopGroup对象时线程数量源码解析
    查看>>
    Netty源码—2.Reactor线程模型一
    查看>>
    Netty源码—4.客户端接入流程一
    查看>>
    Netty源码—4.客户端接入流程二
    查看>>
    Netty源码—5.Pipeline和Handler一
    查看>>
    Netty源码—6.ByteBuf原理二
    查看>>
    Netty源码—7.ByteBuf原理三
    查看>>
    Netty源码—7.ByteBuf原理四
    查看>>
    Netty源码—8.编解码原理二
    查看>>
    Netty源码解读
    查看>>
    Netty的Socket编程详解-搭建服务端与客户端并进行数据传输
    查看>>