Java线程池 ThreadPoolExecutor, Executor

1.线程池的作用

        思考一个问题,我们为什么要引入线程池那么一个概念。首先,要知道我们引入线程就是为了解决频繁创建销毁进程带来的损耗,虽然创建/销毁线程的开销在变小了,但依旧不可忽视,如何进一步优化:

        1.创建一个线程池,提前准备好线程,用完了也不要销毁,减少每次启动,销毁线程的损耗

        2.引入轻量级线程->纤程/协程(Java21引入的虚拟线程),本质是程序猿在用户态代码中进行调度,不需要依靠内核的调度器,节省调度上的开销

2.线程池ThreadPoolExecutor的构造方法

  1. corePoolSize(核心线程数):线程池中保持的最小线程数,即使线程是空闲的也会保留,除非设置了 allowCoreThreadTimeOut。当有任务提交时,即使其他空闲的核心线程可用,也可能会创建新的线程,直到达到核心线程数。

  2. maximumPoolSize(最大线程数):线程池中允许的最大线程数。当活动线程数达到这个值时,后续的任务将被阻塞,直到有线程可用为止。

  3. keepAliveTime(线程空闲时间):当线程池中的线程数量大于核心线程数时,这是多余的空闲线程在终止之前等待新任务的最长时间。

  4. TimeUnit(时间单位):用于指定 keepAliveTime 的时间单位,例如毫秒、秒、分钟等。

  5. workQueue(工作队列):用于保存等待执行的任务的阻塞队列。常见的工作队列包括 ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue 等。

  6. threadFactory(线程工厂):用于创建新线程的工厂。通常情况下,可以通过自定义线程工厂来对线程进行定制,例如设置线程名字、优先级等.这里线程工厂也是一种设计模式,是由工厂类(对构造方法进一步封装后打包成的类,本质是因为构造方法达成方法重载的条件太苛刻)创建对象。

  7. handler(拒绝策略):当工作队列已满且线程池中的线程数达到最大线程数时,新提交的任务将会被拒绝。拒绝策略定义了当任务被拒绝时的处理方式,常见的策略有 AbortPolicy(默认)、CallerRunsPolicyDiscardPolicyDiscardOldestPolicy 等。

    1. AbortPolicy:直接抛出 RejectedExecutionException 异常。
    2. CallerRunsPolicy:让提交任务的线程来执行任务。
    3. DiscardOldestPolicy:丢弃队列中等待最久的任务。
    4. DiscardPolicy:直接丢弃无法执行的任务。

3.Executor类的使用

由于ThreadPoolExecutor使用起来过于麻烦,我们也可以使用 Executors(对ThreadPoolExecutor进一步封装) 创建常见的线程池:

Executors 类提供了一系列静态工厂方法来创建不同类型的线程池。下面是一些常见的线程池创建方法:

  1. newFixedThreadPool(int nThreads):创建一个固定大小的线程池,该线程池中的线程数量始终为指定的数量。

  2. newCachedThreadPool():创建一个根据需要自动调整大小的线程池,该线程池中的线程数量会根据任务的数量动态调整。

  3. newSingleThreadExecutor():创建一个单线程的线程池,该线程池中始终只有一个线程在工作,用于顺序执行任务。

  4. newScheduledThreadPool(int corePoolSize):创建一个可以执行定时任务的线程池,可以指定核心线程数量,用于执行定时任务以及周期性任务。

  5. newWorkStealingPool(int parallelism):创建一个使用工作窃取算法的线程池,用于处理大量并行任务,可以指定并行度。

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) {
        // 创建固定大小的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

        // 创建单线程的线程池
        ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

        // 创建带缓存的线程池,可以根据需要自动创建线程,线程数量不受限制
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        // 创建定时执行任务的线程池
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    }
}

注意:ExecutorService 是一个接口,它是 Java 提供的用于管理线程池的高级工具之一。ThreadPoolExecutor 则是 ExecutorService 接口的一个实现类,它提供了灵活的线程池管理功能,可以通过设置各种参数来自定义线程池的行为

4.线程池的工作流程

线程池的工作流程通:

  1. 当有任务提交到线程池时,线程池会首先检查当前活动线程数是否小于核心线程数,如果是,则会创建新的线程来处理任务,否则进入下一步。

  2. 线程池会将任务添加到工作队列中等待执行。

  3. 如果工作队列已满且当前活动线程数未达到最大线程数,则会创建新的线程来处理任务。

  4. 如果工作队列已满且当前活动线程数已达到最大线程数,则根据指定的拒绝策略来处理新提交的任务。

5.线程数目的确定

需要综合考虑以下几个因素:

  1. 可用的处理器核心数:一般来说,线程数不应该超过处理器核心数,因为多余的线程会竞争 CPU 资源,导致上下文切换频繁,反而降低了性能。

  2. 任务的性质:如果任务是 CPU 密集型的(计算密集型),则线程数不宜过多(不超过核心数),因为大量的线程会增加上下文切换的开销,反而降低了性能。如果任务是 IO 密集型的(如网络请求、文件操作等),则可以适当增加线程数,以充分利用 IO 操作的等待时间。

  3. 任务的数量和大小:如果任务量很大或者任务的执行时间比较长,可以适当增加线程数,以提高任务的响应速度。但是需要注意避免过度创建线程,导致系统资源耗尽或性能下降。

  4. 系统资源:线程数也受到系统资源的限制,包括内存、文件描述符等。如果创建过多的线程会导致系统资源不足,可能会影响系统的稳定性和性能。

  5. 特定应用场景的需求:有些特定的应用场景可能需要特定数量的线程,例如高并发的网络服务器、数据处理系统等。

总之,可以根据具体情况来确定线程池中线程的数量。通常建议先根据系统的配置和需求初步设置线程数,然后通过性能测试和监控来动态调整线程数,以达到最佳的性能和稳定性。

  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值