线程池

作者: Auncle_ | 来源:发表于2019-05-22 18:16 被阅读0次

    1.线程池简介

    1.1 线程池的概念

    线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

    1.2 线程池的工作机制

    在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。

    一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

    1.3 使用线程池的原因

    多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过渡切换线程的危险,从而可能导致系统资源的崩溃。这时,线程池就是最好的选择了

    2.线程池架构(Executor)

    线程池.png

    接口:Executor,CompletionService,ExecutorService,ScheduledExecutorService

    抽象类:AbstractExecutorService

    实现类:ExecutorCompletionService,ThreadPoolExecutor,ScheduledThreadPoolExecutor

    3.ThreadPoolExecutor构造器

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

    corePoolSize :线程池的核心池大小,在创建线程池之后,线程池默认没有任何线程。

    当有任务过来的时候才会去创建创建线程执行任务。换个说法,线程池创建之后,线程池中的线程数为0,当任务过来就会创建一个线程去执行,直到线程数达到corePoolSize 之后,就会被到达的任务放在队列中。(注意是到达的任务)。换句更精炼的话:corePoolSize 表示允许线程池中允许同时运行的最大线程数。

    如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

    maximumPoolSize :线程池允许的最大线程数,他表示最大能创建多少个线程。maximumPoolSize肯定是大于等于corePoolSize。

    keepAliveTime :表示线程没有任务时最多保持多久然后停止。默认情况下,只有线程池中线程数大于corePoolSize 时,keepAliveTime 才会起作用。换句话说,当线程池中的线程数大于corePoolSize,并且一个线程空闲时间达到了keepAliveTime,那么就是shutdown。

    Unit:keepAliveTime 的单位。

    workQueue :一个阻塞队列,用来存储等待执行的任务,当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能

    threadFactory :线程工厂,用来创建线程。

    handler :表示当拒绝处理任务时的策略。

    3.1任务缓存队列

    在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。

    workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:

    1. 有界任务队列ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

    2. 无界任务队列LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

    3. 直接提交队列synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

    3.2 拒绝策略

    AbortPolicy:丢弃任务并抛出RejectedExecutionException

    CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

    DiscardOldestPolicy:丢弃队列中最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。

    DiscardPolicy:丢弃任务,不做任何处理。

    3.3 线程池的任务处理策略:

    如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;

    如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;

    如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

    3.4线程池的关闭

    ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

    shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

    shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

    4.线程池的执行流程

    [图片上传失败...(image-fe5d77-1558520171777)]

    wonrkerCountOf()方法能够取得当前线程池中的线程的总数,取得当前线程数与核心池大小比较,

    如果小于,将通过addWorker()方法调度执行。
    如果大于核心池大小,那么就提交到等待队列。
    如果进入等待队列失败,则会将任务直接提交给线程池。
    如果线程数达到最大线程数,那么就提交失败,执行拒绝策略。

    excute()方法中添加任务的方式是使用addWorker()方法
    ddWorker共有四种传参方式。execute使用了其中三种,分别为:

    1. addWorker(paramRunnable, true)

    线程数小于corePoolSize时,放一个需要处理的task进Workers Set。如果Workers Set长度超过corePoolSize,就返回false.

    1. addWorker(null, false)

    放入一个空的task进workers Set,长度限制是maximumPoolSize。这样一个task为空的worker在线程执行的时候会去任务队列里拿任务,这样就相当于创建了一个新的线程,只是没有马上分配任务。

    1. addWorker(paramRunnable, false)

    当队列被放满时,就尝试将这个新来的task直接放入Workers Set,而此时Workers Set的长度限制是maximumPoolSize。如果线程池也满了的话就返回false.

    还有一种情况是execute()方法没有使用的

    1. addWorker(null, true)

    这个方法就是放一个null的task进Workers Set,而且是在小于corePoolSize时,如果此时Set中的数量已经达到corePoolSize那就返回false,什么也不干。实际使用中是在prestartAllCoreThreads()方法,这个方法用来为线程池预先启动corePoolSize个worker等待从workQueue中获取任务执行。

    4.1 执行流程:

    1. 判断线程池当前是否为可以添加worker线程的状态,可以则继续下一步,不可以return false:
      A、线程池状态>shutdown,可能为stop、tidying、terminated,不能添加worker线程
      B、线程池状态==shutdown,firstTask不为空,不能添加worker线程,因为shutdown状态的线程池不接收新任务
      C、线程池状态==shutdown,firstTask==null,workQueue为空,不能添加worker线程,因为firstTask为空是为了添加一个没有任务的线程再从workQueue获取task,而workQueue为     空,说明添加无任务线程已经没有意义
    2. 线程池当前线程数量是否超过上限(corePoolSize 或 maximumPoolSize),超过了return false,没超过则对workerCount+1,继续下一步
    3. 在线程池的ReentrantLock保证下,向Workers Set中添加新创建的worker实例,添加完成后解锁,并启动worker线程,如果这一切都成功了,return true,如果添加worker入Set失败或启动失败,调用addWorkerFailed()逻辑

    [图片上传失败...(image-9f1a80-1558520171777)]

    5.常见的四种线程池

    //固定大小的线程池
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(new SmsThread());
    //单个线程池
    ExecutorService executorService1 = Executors.newSingleThreadExecutor();
    executorService1.execute(new SmsThread());
    //缓存线程池
    ExecutorService executorService2 = Executors.newCachedThreadPool();
    executorService2.execute(new SmsThread());
    //定时线程池
    ExecutorService executorService3 = Executors.newSingleThreadExecutor();
    executorService3.execute(new SmsThread());
    

    newFixedThreadPool:

    固定大小的线程池,可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。
    该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。同时使用无界的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue
    迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown。

    newSingleThreadExecutor:

    单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。

    newCachedThreadPool:

    缓存线程池,缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列, 他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。

    newScheduledThreadPool:

    定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。

    scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。

    schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间。

    6.线程池的选择

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

    1. newFixedThreadPool和newSingleThreadExecutor:
        主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
    2. newCachedThreadPool和newScheduledThreadPool:
        主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

    相关文章

      网友评论

          本文标题:线程池

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