高薪程序员必修课-Java中为什么不建议使用Executors来创建线程池?

目录

前言

原因分析

1. newFixedThreadPool 和 newSingleThreadExecutor

示例:

2. newCachedThreadPool

示例:

建议的替代方法

示例:

解释:

总结


前言

        在Java中,Executors 类提供了几个工厂方法来创建不同类型的线程池,例如 newFixedThreadPoolnewCachedThreadPoolnewSingleThreadExecutor。虽然这些方法方便使用,但它们在某些情况下可能会带来潜在的问题,因此不建议在生产环境中直接使用 Executors 来创建线程池。下面从原理角度详细讲解原因,并提供相应的示例。

原因分析

1. newFixedThreadPoolnewSingleThreadExecutor

原理:这两个方法使用无界队列 LinkedBlockingQueue 作为任务队列。

问题:无界队列意味着队列可以无限增长。如果任务提交速度快于任务处理速度,队列中的任务会不断增加,可能导致内存耗尽(OOM)。

示例:
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
    executor.submit(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
}

在这个示例中,如果任务提交速度非常快,任务会无限制地排队,最终导致内存耗尽。

2. newCachedThreadPool

原理:该方法使用 SynchronousQueue 作为任务队列,并且线程池的最大线程数是 Integer.MAX_VALUE

问题SynchronousQueue 不存储任务,每个插入操作必须等待相应的移除操作。如果任务提交速度快于任务处理速度,会创建大量线程,可能导致系统资源耗尽(如CPU、内存等)。

示例:
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
    executor.submit(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
}

在这个示例中,如果任务提交速度非常快,会创建大量线程,最终导致系统资源耗尽。

建议的替代方法

为了更好地控制线程池的行为,建议使用 ThreadPoolExecutor 构造函数来创建线程池。这样可以更明确地指定线程池的参数,如核心线程数、最大线程数、任务队列类型和拒绝策略。

示例:
import java.util.concurrent.*;

public class CustomThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个具有自定义参数的线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                10,                        // 核心线程数
                20,                        // 最大线程数
                60L,                       // 非核心线程的空闲时间
                TimeUnit.SECONDS,          // 空闲时间的单位
                new ArrayBlockingQueue<>(100),  // 有界任务队列
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );

        for (int i = 0; i < 200; i++) {
            executor.submit(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println(Thread.currentThread().getName() + " completed task");
            });
        }

        executor.shutdown();
    }
}
解释:
  1. 核心线程数和最大线程数

    • 核心线程数设置为10,最大线程数设置为20。
    • 线程池在初始时创建10个核心线程,任务提交超过10个时,会创建最多10个额外的线程(最大线程数-核心线程数)。
  2. 非核心线程的空闲时间

    • 非核心线程空闲时间设置为60秒,超过这个时间未处理任务的非核心线程将被终止。
  3. 有界任务队列

    • 使用 ArrayBlockingQueue,容量为100。这限制了任务队列的大小,避免无限增长导致内存耗尽。
  4. 拒绝策略

    • 使用 CallerRunsPolicy 作为拒绝策略。当任务队列已满且所有线程都在运行时,拒绝策略将任务返回给调用者线程执行,避免任务丢失。

总结

不建议使用 Executors 创建线程池的原因主要是它隐藏了线程池的具体实现,容易导致以下问题:

  1. 无界队列导致内存耗尽newFixedThreadPoolnewSingleThreadExecutor 使用无界队列,可能导致内存耗尽。
  2. 线程无限增长导致资源耗尽newCachedThreadPool 使用 SynchronousQueue 和无限制的最大线程数,可能导致系统资源耗尽。

使用 ThreadPoolExecutor 构造函数可以更精细地控制线程池的参数,从而避免这些潜在问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值