线程池的创建

1.Executors创建的问题

对于线程池的创建,在一开始学习javaSE使用的是Executors类里的静态方法,但是这里创建线程池会有很多问题。

对于 newFixedThreadPool 和 newSingleThreadExecutor方法

这两个方法在创建线程池时使用的是 LinkedBlockingQueue 作为任务队列,这个队列的默认大小为 Integer.MAX_VALUE如果任务提交速度远高于处理速度,队列会不断堆积任务,最终导致内存溢出(OOM)。

对于newCachedThreadPool 和 newScheduledThreadPool

这两个方法默认线程数Integer.MAX_VALUE如果任务量激增,会创建大量线程,导致系统资源耗尽。

2.正确的创建方法

为了避免潜在的风险,更好地管理线程池,阿里巴巴开发手册建议使用 ThreadPoolExecutor 来手动创建线程池,以便根据具体的业务需求灵活配置线程池的参数。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

对应的参数介绍:

①. corePoolSize & maximumPoolSize

核心线程数(corePoolSize)和最大线程数(maximumPoolSize)是线程池中非常重要的两个概念。

当新任务提交时:

  • 如果当前运行线程数 < corePoolSize,即使有空闲线程,也会创建新线程执行任务。

  • 当前运行线程数 ≥ corePoolSize,任务先进【队列等待】。

  • 只有队列已满 & 当前运行线程数 < maximumPoolSize,才会新建线程。

②. keepAliveTime & unit

通过这种机制,线程池能够在任务负载减少时自动减少线程数量,从而节省系统资源。

  • keepAliveTime 为超过 corePoolSize 线程数量的线程最大空闲时间;

  • 当线程池中的线程数量超过 corePoolSize 时,如果有多余的线程处于空闲状态(即没有任务可执行),这些空闲线程不会立即被销毁。keepAliveTime 指定了这些空闲线程在被终止之前可以等待新任务的最长时间。

  • unit 为时间单位。

  • 具体的:当一个空闲线程在 ThreadPoolExecutor 中执行完一个任务后,如果该线程没有立即接收到新的任务,它会进入空闲状态。此时,keepAliveTime 会重新开始计时。

    具体来说,keepAliveTime 的计时逻辑如下:

  • 任务执行完毕:线程执行完一个任务后,会检查任务队列中是否有新的任务可执行。

  • 进入空闲状态:如果没有新的任务可执行,线程会进入空闲状态。

  • 重新计时:进入空闲状态后,keepAliveTime 开始计时。如果在 keepAliveTime 规定的时间内没有新的任务到达,该线程将被终止(前提是该线程数量超过 corePoolSize,或者 allowCoreThreadTimeOut(true) 被调用)。

  • 新任务到达:如果在 keepAliveTime 内有新任务到达,线程会被重新激活以执行新任务,keepAliveTime 的计时会在下次进入空闲状态时重新开始。

③. 等待队列(workQueue)

用于存放等待执行任务的队列。当前运行的线程数量超过 corePoolSize 时,新任务会被放入该队列中等待执行。

下面来看一下三种通用的入队策略:

  1. 无界队列(LinkedBlockingQueue):
    • 特点:队列容量无限(Integer.MAX_VALUE),任务可以一直添加到队列中。也因此,不会触发 maximumPoolSize。

    • 适用场景:适用于任务量大但不希望频繁创建线程的场景,比如 Tomcat。

    • 注意事项:如果任务提交速度远高于处理速度,队列会无限增长,最终导致内存溢出(OOM)。

  2. 有界队列 (ArrayBlockingQueue):
    • 特点:队列容量固定,任务数超过容量时会触发创建新线程(如果线程数未达最大线程数)。

    • 适用场景:适用于控制并发量,避免资源耗尽。

    • 注意事项:队列容量需要根据业务需求合理设置,过小可能导致频繁触发拒绝策略。

  3. 同步队列 (SynchronousQueue):
    • 特点:不存储任务,直接将任务交给空闲线程处理;如果没有空闲线程,则创建新线程(如果线程数未达最大线程数)。

    • 适用场景:任务量较小且需要快速响应的场景,例如高并发短任务处理。

    • 注意事项:线程不足时,任务可能被拒绝。

④. 线程工厂 (threadFactory)

用于创建线程的工厂类,通过它可以自定义线程的创建方式,如设置线程的名称、优先级、是否为守护线程等。

⑤. 拒绝策略 (handler)

当任务队列已满且线程数量达到 maximumPoolSize 时,新提交的任务会被拒绝。

下面来看一下四种常见的拒绝策略:

  1. AbortPolicy(默认):直接抛出 RejectedExecutionException 异常。

  2. CallerRunsPolicy:由提交任务的线程直接执行该任务

  3. DiscardPolicy:直接丢弃新任务,不抛异常。

  4. DiscardOldestPolicy:丢弃任务队列中最老的任务,然后尝试重新提交被拒绝的任务。

一个例子:

 public static void main(String[] args) {
        // 创建一个线程池,核心线程数为2,最大线程数为4,空闲线程存活时间为10秒
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,
                4,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(2),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        // 提交任务给线程池
        for (int i = 0; i < 6; i++) {
            final int taskNumber = i;
            executor.execute(() -> {
                System.out.println("任务 " + taskNumber + " 正在由线程 " + Thread.currentThread().getName() + " 执行");
                try {
                    // 模拟任务执行时间
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println("任务 " + taskNumber + " 执行完毕");
            });
        }

        // 关闭线程池
        executor.shutdown();
        try {
            // 等待所有任务完成
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
        }


    }

执行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值