从一条阿里编码规范谈线程池的使用

最近在看阿里Java编程规范,有一条引起了我的注意:

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:

  1. FixedThreadPoolSingleThreadPool: 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
  2. CachedThreadPoolScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

平时没怎么注意这一点,一般用的多的还是FixedThreadPoolSingleThreadPool。那怎么理解这条规范呢?首先,ThreadPoolExecutor是更底层的类,直接用它的构造方法,可以让我们对创建的线程池有更强的控制。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}

我们来看下ThreadPoolExecutor的参数设定:

  • corePoolSize:核心线程数(就算有空闲线程,也不会低于这个数)
  • maximumPoolSize:最大线程数
  • keepAliveTime:最大存活时间(超过corePoolSize数量的线程空闲下最大存活时间)
  • unit:时间单位
  • workQueue:工作队列(核心线程满后,任务会进入这里)
  • handler:任务满负荷(超出最大线程数和工作队列上限之和)后的处理策略

其中处理策略有四种:

  • AbortPolicy:默认策略,在需要拒绝任务时抛出RejectedExecutionException
  • CallerRunsPolicy:直接在 execute 方法的调用线程中运行被拒绝的任务,如果线程池已经关闭,任务将被丢弃
  • DiscardPolicy:直接丢弃任务
  • DiscardOldestPolicy:丢弃队列中等待时间最长的任务,并执行当前提交的任务,如果线程池已经关闭,任务将被丢弃

除此之外我们也可以自定义处理策略。

作为对比,我们看下FixedThreadPool的实现:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

可见它实际上就是个简化版的ThreadPoolExecutor,通过固定线程数,然后指定工作队列长度为无限(不传长度代表容量无限)。这样确实可能会造成任务无限堆积,引起OOM的问题。所以阿里的规范建议我们直接使用更底层的ThreadPoolExecutor,把处理策略之类的都提前设定好。

不过规范也不是死的,在实际情况中也无法避免使用FixedThreadPool等更高层的实现。一方面是因为使用起来更加方便,另一方面也是有时候使用ThreadPoolExecutor并没有明显的好处。例如:线程池中分配的任务都是关键任务,无法被丢弃,然后因为性能考虑也无法被execute线程同步执行,这样就很难定义一个合适的处理策略,不如直接使用FixedThreadPool。就算有OOM的风险,那也只能说在多种风险权衡下做的一个评估和取舍。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Java中手动创建符合阿里规范的线程池,您可以使用`java.util.concurrent.ThreadPoolExecutor`类进行自定义配置。以下是一个示例: ```java import java.util.concurrent.*; public class Main { public static void main(String[] args) { int corePoolSize = 5; // 核心线程数 int maxPoolSize = 10; // 最大线程数 long keepAliveTime = 60; // 线程空闲时间(秒) int queueCapacity = 100; // 阻塞队列容量 ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 线程工厂 RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy(); // 拒绝策略 // 创建线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueCapacity), threadFactory, rejectedExecutionHandler ); // 提交任务给线程池 for (int i = 0; i < 10; i++) { Runnable worker = new WorkerThread("Task " + (i + 1)); executor.execute(worker); } // 关闭线程池 executor.shutdown(); try { if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); } System.out.println("所有任务已完成"); } } class WorkerThread implements Runnable { private String taskName; public WorkerThread(String taskName) { this.taskName = taskName; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " 开始执行任务: " + taskName); // 执行任务的逻辑代码 System.out.println(Thread.currentThread().getName() + " 完成任务: " + taskName); } } ``` 在上面的示例中,我们手动创建了一个线程池,其中包括了一些符合阿里规范的配置: - 核心线程数为5,最大线程数为10; - 线程空闲时间为60秒,超过空闲时间的线程将被回收; - 阻塞队列容量为100,超过容量的任务将被拒绝执行; - 使用默认的线程工厂和拒绝策略; - 在关闭线程池时,使用`awaitTermination()`方法等待最多10秒钟,如果超时仍有任务未完成,则强制关闭线程池。 请注意,这只是一个示例,您可以根据自己的需求进行适当的调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值