为什么阿里内部不允许用Executors创建线程池?

来源:cnblogs.com/zjfjava/p/11227456.html

1. 通过Executors创建线程池的弊端

在创建线程池的时候,大部分人还是会选择使用Executors去创建。

下面是创建定长线程池(FixedThreadPool)的一个例子,严格来说,当使用如下代码创建线程池时,是不符合编程规范的。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

原因在于:(摘自阿里编码规约)

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。说明:Executors各个方法的弊端:

1)newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

2)newCachedThreadPool和newScheduledThreadPool:   主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

2. 通过ThreadPoolExecutor创建线程池

所以,针对上面的不规范代码,重构为通过ThreadPoolExecutor创建线程池的方式。

    /*** Creates a new {@code ThreadPoolExecutor} with the given initial* parameters and default thread factory.** @param corePoolSize the number of threads to keep in the pool, even*        if they are idle, unless {@code allowCoreThreadTimeOut} is set* @param maximumPoolSize the maximum number of threads to allow in the*        pool* @param keepAliveTime when the number of threads is greater than*        the core, this is the maximum time that excess idle threads*        will wait for new tasks before terminating.* @param unit the time unit for the {@code keepAliveTime} argument* @param workQueue the queue to use for holding tasks before they are*        executed.  This queue will hold only the {@code Runnable}*        tasks submitted by the {@code execute} method.* @param handler the handler to use when execution is blocked*        because the thread bounds and queue capacities are reached* @throws IllegalArgumentException if one of the following holds:<br>*         {@code corePoolSize < 0}<br>*         {@code keepAliveTime < 0}<br>*         {@code maximumPoolSize <= 0}<br>*         {@code maximumPoolSize < corePoolSize}* @throws NullPointerException if {@code workQueue}*         or {@code handler} is null*/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 是线程池的核心实现。线程的创建和终止需要很大的开销,线程池中预先提供了指定数量的可重用线程,所以使用线程池会节省系统资源,并且每个线程池都维护了一些基础的数据统计,方便线程的管理和监控。

3. ThreadPoolExecutor参数解释

下面是对其参数的解释,在创建线程池时需根据自己的情况来合理设置线程池。

corePoolSize & maximumPoolSize

核心线程数(corePoolSize)和最大线程数(maximumPoolSize)是线程池中非常重要的两个概念,希望同学们能够掌握。当一个新任务被提交到池中,如果当前运行线程小于核心线程数(corePoolSize),即使当前有空闲线程,也会新建一个线程来处理新提交的任务;如果当前运行线程数大于核心线程数(corePoolSize)并小于最大线程数(maximumPoolSize),只有当等待队列已满的情况下才会新建线程。

keepAliveTime & unit

keepAliveTime 为超过 corePoolSize 线程数量的线程最大空闲时间,unit 为时间单位。

等待队列

任何阻塞队列(BlockingQueue)都可以用来转移或保存提交的任务,线程池大小和阻塞队列相互约束线程池:

  1. 如果运行线程数小于corePoolSize,提交新任务时就会新建一个线程来运行;

  2. 如果运行线程数大于或等于corePoolSize,新提交的任务就会入列等待;如果队列已满,并且运行线程数小于maximumPoolSize,也将会新建一个线程来运行;

  3. 如果线程数大于maximumPoolSize,新提交的任务将会根据拒绝策略来处理。

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

  1. 直接传递:通过 SynchronousQueue 直接把任务传递给线程。如果当前没可用线程,尝试入队操作会失败,然后再创建一个新的线程。当处理可能具有内部依赖性的请求时,该策略会避免请求被锁定。直接传递通常需要无界的最大线程数(maximumPoolSize),避免拒绝新提交的任务。当任务持续到达的平均速度超过可处理的速度时,可能导致线程的无限增长。

  2. 无界队列:使用无界队列(如 LinkedBlockingQueue)作为等待队列,当所有的核心线程都在处理任务时, 新提交的任务都会进入队列等待。因此,不会有大于 corePoolSize 的线程会被创建(maximumPoolSize 也将失去作用)。这种策略适合每个任务都完全独立于其他任务的情况;例如网站服务器。这种类型的等待队列可以使瞬间爆发的高频请求变得平滑。当任务持续到达的平均速度超过可处理速度时,可能导致等待队列无限增长。

  3. 有界队列:当使用有限的最大线程数时,有界队列(如 ArrayBlockingQueue)可以防止资源耗尽,但是难以调整和控制。队列大小和线程池大小可以相互作用:使用大的队列和小的线程数可以减少CPU使用率、系统资源和上下文切换的开销,但是会导致吞吐量变低,如果任务频繁地阻塞(例如被I/O限制),系统就能为更多的线程调度执行时间。使用小的队列通常需要更多的线程数,这样可以最大化CPU使用率,但可能会需要更大的调度开销,从而降低吞吐量。

拒绝策略

当线程池已经关闭或达到饱和(最大线程和队列都已满)状态时,新提交的任务将会被拒绝。ThreadPoolExecutor 定义了四种拒绝策略:

  1. AbortPolicy:默认策略,在需要拒绝任务时抛出RejectedExecutionException;

  2. CallerRunsPolicy:直接在 execute 方法的调用线程中运行被拒绝的任务,如果线程池已经关闭,任务将被丢弃;

  3. DiscardPolicy:直接丢弃任务;

  4. DiscardOldestPolicy:丢弃队列中等待时间最长的任务,并执行当前提交的任务,如果线程池已经关闭,任务将被丢弃。

我们也可以自定义拒绝策略,只需要实现 RejectedExecutionHandler;需要注意的是,拒绝策略的运行需要指定线程池和队列的容量。

4. ThreadPoolExecutor创建线程方式

通过下面的demo来了解ThreadPoolExecutor创建线程的过程。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*** 测试ThreadPoolExecutor对线程的执行顺序**/
public class ThreadPoolSerialTest {public static void main(String[] args) {//核心线程数int corePoolSize = 3;//最大线程数int maximumPoolSize = 6;//超过 corePoolSize 线程数量的线程最大空闲时间long keepAliveTime = 2;//以秒为时间单位TimeUnit unit = TimeUnit.SECONDS;//创建工作队列,用于存放提交的等待执行任务BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(2);ThreadPoolExecutor threadPoolExecutor = null;try {//创建线程池threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,new ThreadPoolExecutor.AbortPolicy());//循环提交任务for (int i = 0; i < 8; i++) {//提交任务的索引final int index = (i + 1);threadPoolExecutor.submit(() -> {//线程打印输出System.out.println("大家好,我是线程:" + index);try {//模拟线程执行时间,10sThread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}});//每个任务提交后休眠500ms再提交下一个任务,用于保证提交顺序Thread.sleep(500);}} catch (InterruptedException e) {e.printStackTrace();} finally {threadPoolExecutor.shutdown();}}
}

执行结果:

这里描述一下执行的流程:

  • 首先通过 ThreadPoolExecutor 构造函数创建线程池;

  • 执行 for 循环,提交 8 个任务(恰好等于maximumPoolSize[最大线程数] + capacity[队列大小]);

  • 通过 threadPoolExecutor.submit 提交 Runnable 接口实现的执行任务;

  • 提交第1个任务时,由于当前线程池中正在执行的任务为 0 ,小于 3(corePoolSize 指定),所以会创建一个线程用来执行提交的任务1;

  • 提交第 2, 3 个任务的时候,由于当前线程池中正在执行的任务数量小于等于 3 (corePoolSize 指定),所以会为每一个提交的任务创建一个线程来执行任务;

  • 当提交第4个任务的时候,由于当前正在执行的任务数量为 3 (因为每个线程任务执行时间为10s,所以提交第4个任务的时候,前面3个线程都还在执行中),此时会将第4个任务存放到 workQueue 队列中等待执行;

  • 由于 workQueue 队列的大小为 2 ,所以该队列中也就只能保存 2 个等待执行的任务,所以第5个任务也会保存到任务队列中;

  • 当提交第6个任务的时候,因为当前线程池正在执行的任务数量为3,workQueue 队列中存储的任务数量也满了,这时会判断当前线程池中正在执行的任务的数量是否小于6(maximumPoolSize指定);

  • 如果小于 6 ,那么就会新创建一个线程来执行提交的任务 6;

  • 执行第7,8个任务的时候,也要判断当前线程池中正在执行的任务数是否小于6(maximumPoolSize指定),如果小于6,那么也会立即新建线程来执行这些提交的任务;

  • 此时,6个任务都已经提交完毕,那 workQueue 队列中的等待 任务4 和 任务5 什么时候执行呢?

  • 当任务1执行完毕后(10s后),执行任务1的线程并没有被销毁掉,而是获取 workQueue 中的任务4来执行;

  • 当任务2执行完毕后,执行任务2的线程也没有被销毁,而是获取 workQueue 中的任务5来执行;

通过上面流程的分析,也就知道了之前案例的输出结果的原因。其实,线程池中会线程执行完毕后,并不会被立刻销毁,线程池中会保留 corePoolSize 数量的线程,当 workQueue 队列中存在任务或者有新提交任务时,那么会通过线程池中已有的线程来执行任务,避免了频繁的线程创建与销毁,而大于 corePoolSize 小于等于 maximumPoolSize 创建的线程,则会在空闲指定时间(keepAliveTime)后进行回收。

5. ThreadPoolExecutor拒绝策略

在上面的测试中,我设置的执行线程总数恰好等于maximumPoolSize[最大线程数] + capacity[队列大小],因此没有出现需要执行拒绝策略的情况,因此在这里,我再增加一个线程,提交9个任务,来演示不同的拒绝策略。

AbortPolicy

CallerRunsPolicy

DiscardPolicy

DiscardOldestPolicy
参考:www.jianshu.com/p/6f82b738ac58

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544879.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ中7种消息队列和保姆级代码演示!

blog.csdn.net/qq_32828253/article/details/110450249七种模式介绍与应用场景简单模式&#xff08;Hello World&#xff09;做最简单的事情&#xff0c;一个生产者对应一个消费者&#xff0c;RabbitMQ相当于一个消息代理&#xff0c;负责将A的消息转发给B应用场景&#xff1a;…

CyclicBarrier:人齐了,老司机就发车了!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;上一篇咱讲了 CountDownLatch 可以解决多个线程同步的问题&#xff0c;相比于 join 来说它的应用范围更广&#xff0c;不仅可…

iOS平台快速发布HT for Web拓扑图应用

iOS平台一直是封闭的生态圈&#xff0c;iOS开发者要缴纳年费加入开发者计划才可进行iOS平台的APP开发测试&#xff0c;所开发的APP需要上传到App Store经过苹果审核以后才可对外发布。如果要开发企业内部应用&#xff0c;则要缴纳更高的费用购买企业账户才可以。 对于现在火如荼…

事务注解 @Transactional 失效的3种场景及解决办法

Transactional失效场景第一种 Transactional注解标注方法修饰符为非public时&#xff0c;Transactional注解将会不起作用。例如以下代码&#xff0c;定义一个错误的Transactional标注实现&#xff0c;修饰一个默认访问符的方法&#xff1a;/*** author zhoujy**/ Component pub…

Android的多语言实现

文章转自&#xff1a;http://blog.csdn.net/barryhappy/article/details/23436527 以前就知道Android的多语言实现很简单&#xff0c;可以在不同的语言环境下使用不同的资源什么的&#xff0c;但是一直没有实际使用过。 最近公司的项目要用到多语言于&#xff0c;是就研究了一下…

厉害了,自己手写一个Java热加载!

热加载&#xff1a;在不停止程序运行的情况下&#xff0c;对类&#xff08;对象&#xff09;的动态替换。Java ClassLoader 简述Java中的类从被加载到内存中到卸载出内存为止&#xff0c;一共经历了七个阶段&#xff1a;加载、验证、准备、解析、初始化、使用、卸载。接下来我们…

公司新来的小可爱,竟然把内存搞崩了!

ThreadLocal使用不规范&#xff0c;师傅两行泪组内来了一个实习生&#xff0c;看这小伙子春光满面、精神抖擞、头发微少&#xff0c;我心头一喜&#xff1a;绝对是个潜力股。于是我找经理申请亲自来带他&#xff0c;为了帮助小伙子快速成长&#xff0c;我给他分了一个需求&…

理解Node.js的event loop

为什么80%的码农都做不了架构师&#xff1f;>>> 关于Node.js的第一个基本概念是I/O操作开销是巨大的&#xff1a; 所以&#xff0c;当前变成技术中最大的浪费来自于等待I/O操作的完成。有几种方法可以解决性能的影响&#xff1a; 同步方式&#xff1a;按次序一个…

硬核|定时任务的10种实现方案,满足你的不同需求!

最近有几个读者私信给我&#xff0c;问我他们的业务场景&#xff0c;要用什么样的定时任务。确实&#xff0c;在不用的业务场景下要用不同的定时任务&#xff0c;其实我们的选择还是挺多的。我今天给大家总结10种非常实用的定时任务&#xff0c;总有一种是适合你的。一. linux自…

Semaphore自白:限流器用我就对了!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;大家好&#xff0c;我是 Semaphore&#xff0c;我的中文名字叫“信号量”&#xff0c;我来自 JUC 家族&#xff08;java.uti…

Android Activity和Intent机制学习笔记

文章转自&#xff1a;http://www.cnblogs.com/feisky/archive/2010/01/16/1649081.html Activity Android中&#xff0c;Activity是所有程序的根本&#xff0c;所有程序的流程都运行在Activity之中&#xff0c;Activity具有自己的生命周期&#xff08;见http://www.cnblogs.com…

线程的故事:我的3位母亲成就了优秀的我!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;声明&#xff1a;本故事纯属虚构&#xff0c;如果雷同那就是真事了&#xff01;大家好&#xff0c;我是线程&#xff0c;我的…

7种内存泄露场景和13种解决方案!

前言Java通过垃圾回收机制&#xff0c;可以自动的管理内存&#xff0c;这对开发人员来说是多么美好的事啊。但垃圾回收器并不是万能的&#xff0c;它能够处理大部分场景下的内存清理、内存泄露以及内存优化。但它也并不是万能的。不然&#xff0c;我们在实践的过程中也不会出现…

Android Sqlite

2019独角兽企业重金招聘Python工程师标准>>> 一、http://lansuiyun.iteye.com/blog/1246430 good! 二、增加新表时&#xff0c;需要更新版本号。 三、Android 使用自定义cursorAdapter&#xff1a; http://blog.csdn.net/buaalei/article/details/6064792 good! 四…

小黑小波比.git clone报错解决方案

2019独角兽企业重金招聘Python工程师标准>>> zmzpzmzp1:~/data$ git clone git192.168.199.199:zmw/s910.git 正克隆到 s910... ssh: connect to host 192.168.199.199 port 22: Connection refused fatal: Could not read from remote repository.Please make sure…

一个sql注入直接把我们服务搞挂了

前言最近我在整理安全漏洞相关问题&#xff0c;准备在公司做一次分享。恰好&#xff0c;这段时间团队发现了一个sql注入漏洞&#xff1a;在一个公共的分页功能中&#xff0c;排序字段作为入参&#xff0c;前端页面可以自定义。在分页sql的mybatis mapper.xml中&#xff0c;orde…

reinterpret_cast和static_cast的总结

主要参考&#xff1a;http://blog.csdn.net/querw/article/details/7387594 http://www.cnblogs.com/jerry19880126/archive/2012/08/14/2638192.html http://www.cnblogs.com/ider/archive/2011/07/30/cpp_cast_operator_part3.htmlhttp://bbs.csdn.net/topics/390249118 关键…

Java双刃剑之Unsafe类详解

前一段时间在研究juc源码的时候&#xff0c;发现在很多工具类中都调用了一个Unsafe类中的方法&#xff0c;出于好奇就想要研究一下这个类到底有什么作用&#xff0c;于是先查阅了一些资料&#xff0c;一查不要紧&#xff0c;很多资料中对Unsafe的态度都是这样的画风&#xff1a…

Java知多少(66)输入输出(IO)和流的概述

输入输出&#xff08;I/O&#xff09;是指程序与外部设备或其他计算机进行交互的操作。几乎所有的程序都具有输入与输出操作&#xff0c;如从键盘上读取数据&#xff0c;从本地或网络上的文件读取数据或写入数据等。通过输入和输出操作可以从外界接收信息&#xff0c;或者是把信…

额!Java中用户线程和守护线程区别这么大?

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在 Java 语言中线程分为两类&#xff1a;用户线程和守护线程&#xff0c;而二者之间的区别却鲜有人知&#xff0c;所以本文磊…