美文网首页
阿里规范要求不能使用Executors创建线程

阿里规范要求不能使用Executors创建线程

作者: 步二小哥 | 来源:发表于2019-10-05 22:56 被阅读0次

    什么是线程池

    线程池可以通过池看出来是一个资源集,任何池的作用都大同小异,主要是用来减少资源创建、初始化的系统开销。

    创建线程很“贵”吗

    是的。创建线程的代价是昂贵的。

    我们都知道系统中的每个进程有自己独立的内存空间,而被称为轻量级进程的线程也是需要的。

    在JVM中默认一个线程需要使用256k~1M(取决于32位还是64位操作系统)的内存。(具体的数组我们不深究,因为随着JVM版本的变化这个默认值随时可能发生变更,我们只需要知道线程是需要占用内存的)

    许多文章会将上下文切换、CPU调度列入其中,这边不将线程调度列入是因为睡眠中的线程不会被调度(OS控制),如果不是睡眠中的线程那么是一定需要被调度的。

    但在JVM中除了创建时的内存消耗,还会给GC带来压力,如果频繁创建线程那么相对的GC的时候也需要回收对应的线程。

    线程池的机制

    可以看到线程池是一种重复利用线程的技术,线程池的主要机制就是保留一定的线程数在没有事情做的时候使之睡眠,当有活干的时候拿一个线程去运行。

    这些牵扯到线程池实现的具体策略。

    线程池的好处

    第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

    第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌

    线程池的主要工作流程

    从上图我们可以看出,当提交一个新任务到线程池时,线程池的处理流程如下:

    首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。

    其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。

    最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。

    线程池的创建

    我们可以通过ThreadPoolExecutor来创建一个线程池。

    new ThreadPoolExecutor(corePoolSize, maximumPoolSize,

    keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler);

    创建一个线程池需要输入几个参数:

    1、corePoolSize - 线程池核心池的大小。

    2、maximumPoolSize - 线程池的最大线程数。

    3、keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

    4、unit - keepAliveTime 的时间单位。

    5、workQueue - 用来储存等待执行任务的队列。

    6、threadFactory - 线程工厂。

    7、handler - 拒绝策略。

    corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

    maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

    keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

    unit:参数keepAliveTime的时间单位,有7种取值。TimeUnit.DAYS、TimeUnit.HOURS、TimeUnit.MINUTES、TimeUnit.SECONDS、TimeUnit.MILLISECONDS、TimeUnit.MICROSECONDS、TimeUnit.NANOSECONDS

    workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。 

    ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和SynchronousQueue。线程池的排队策略与BlockingQueue有关。

    threadFactory:线程工厂,主要用来创建线程;

    handler:表示当拒绝处理任务时的策略,有以下四种取值: 

    ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 

    ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 

    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) 

    ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

    当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

    线程池不允许使用Executors去创建

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

    1)newFixedThreadPool和newSingleThreadExecutor

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

    newSingleThreadExecutor

    创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。

    此线程池保证所有任务的执行顺序,按照任务的提交顺序(FIFO, LIFO, 优先级)执行。

    newFixedThreadPool

    创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。

    线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

    可控制线程最大并发数,超出的线程会在队列中等待

    2)newCachedThreadPool和newScheduledThreadPool

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

    newCachedThreadPool

    创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

    那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。

    此线程池不会对线程池大小做限制

    线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

    newScheduledThreadPool

    创建一个定时线程池,支持定时及周期性任务执行

    图1 图2 图3

    线程池创建方式(推荐)

    根据阿里巴巴java开发规范,推荐了3种线程池创建方式

    推荐方式1:

    首先引入:commons-lang3包

    推荐方式 2:

    首先引入:com.google.guava包

    推荐方式 3:

    spring配置线程池方式:自定义线程工厂bean需要实现ThreadFactory,可参考该接口的其它默认实现类,使用方式直接注入bean

    调用execute(Runnable task)方法即可

    线程使用示例

    创建线程工厂方法

    package com.guo.test.app.common.util

    import com.google.common.util.concurrent.ThreadFactoryBuilder;

    import java.util.concurrent.*;

    public class AsynExecutorUtil {

    public static final ThreadFactoryNAMED_THREAD_FACTORY =new ThreadFactoryBuilder().setNameFormat("test_asyn_executor").build();

        public static final ExecutorServiceEXECUTOR            =new ThreadPoolExecutor(10, 20, 0L,

                                                                                          TimeUnit.MILLISECONDS,

                                                                                          new LinkedBlockingQueue(1024),

                                                                                          NAMED_THREAD_FACTORY,

                                                                                          new ThreadPoolExecutor.AbortPolicy());

    }

    异步使用线程:

    AsynExecutorUtil.EXECUTOR.submit(()->testBO.testAsynExecutor(id));

    参考:

    https://www.cnblogs.com/ants/p/11343657.html

    https://www.toutiao.com/i6739759947166253580/

    https://blog.csdn.net/weixin_41888813/article/details/90769126

    相关文章

      网友评论

          本文标题:阿里规范要求不能使用Executors创建线程

          本文链接:https://www.haomeiwen.com/subject/lepypctx.html