线程池ThreadPoolExecutor 原理分析

线程池的分析

线程重用

在实际生产环境中,线程的数量必须得到控制,盲目的大量创建线程对系统性能是有伤害的。为了避免系统频繁的创建和销毁线程,我们可以让创建的线程进行复用。线程池就是为了实现线程的复用。在线程池中维护一定数量的线程,用来反复的执行提交的任务。

线程重用的核心是,它把Thread.start()给屏蔽起来了(一定不要重复调用),所以要重用Thread,就不能让Thread执行完一个任务后终止,因此就必须阻塞Thread.run()方法,让该方法不停地从任务队列中获取任务并执行。循环在跑的过程中不断检查我们是否有新加入的子Runnable对象,有就调一下我们的run(),其实就一个大run()把其它小run()#1,run()#2,…给串联起来了,基本原理就这么简单。

线程池的优点

  1. 重用存在的线程,减少对象创建、消亡的开销,提高性能
  2. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
  3. 提供定时执行、定期执行、单线程、并发数控制等功能

ThreadPoolExecutor概述

  1. ThreadPoolExecutor继承自AbstractExecutorService,也是实现了ExecutorService接口。
  2. ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等等服务。
  3. Executors方法提供的线程服务,都是通过参数设置来实现不同的线程池机制。
    构造方法参数讲解 :
    corePoolSize:核心线程池大小
    maximumPoolSize:最大线程池大小
    keepAliveTime :线程池中超过corePoolSize数目的空闲线程最大存活时间;
    allowCoreThreadTimeOut(true)使得核心线程有效时间。线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTim
    TimeUnit:keepAliveTime时间单位
    workQueue:阻塞任务队列。保存等待执行的任务的阻塞队列,当提交一个新的任务到线程池以后, 线程池会根据当前线程池中正在运行着的线程的数量来决定对该任务的处理方式,主要有以下几种处理方式:
  • 直接切换:这种方式常用的队列是SynchronousQueue;

  • 使用无界队列:一般使用基于链表的阻塞队列LinkedBlockingQueue。如果使用这种方式,那么线程池中能够创建的最大线程数就是corePoolSize,而maximumPoolSize就不会起作用了(后面也会说到)。当线程池中所有的核心线程都是RUNNING状态时,这时一个新的任务提交就会放入等待队列中。

  • 使用有界队列:一般使用ArrayBlockingQueue。使用该方式可以将线程池的最大线程数量限制为maximumPoolSize,这样能够降低资源的消耗,但同时这种方式也使得线程池对线程的调度变得更困难,因为线程池和队列的容量都是有限的值,所以要想使线程池处理任务的吞吐率达到一个相对合理的范围,又想使线程调度相对简单,并且还要尽可能的降低线程池对资源的消耗,就需要合理的设置这两个数量。

    如果要想降低系统资源的消耗(包括CPU的使用率,操作系统资源的消耗,上下文环境切换的开销等), 可以设置较大的队列容量和较小的线程池容量, 但这样也会降低线程处理任务的吞吐量。

    如果提交的任务经常发生阻塞,那么可以考虑通过调用 setMaximumPoolSize() 方法来重新设定线程池的容量。如果队列的容量设置的较小,通常需要将线程池的容量设置大一点,这样CPU的使用率会相对的高一些。但如果线程池的容量设置的过大,则在提交的任务数量太多的情况下,并发量会增加,那么线程之间的调度就是一个要考虑的问题,因为这样反而有可能降低处理任务的吞吐量。

    threadFactory :新建线程工厂。它是 ThreadFactory 类型的变量,用来创建新线程。默认使用 Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory 来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。
    RejectedExecutionHandler:当提交任务数超过maxmumPoolSize + workQueue之和时,任务会交给RejectedExecutionHandler 来处理。它是RejectedExecutionHandler 类型的变量,表示线程池的饱和策略。如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。线程池提供了4种策略:AbortPolicy(直接抛出异常,这是默认策略)、CallerRunsPolicy (用调用者所在的线程来执行任务)、DiscardOldestPolicy(丢弃阻塞队列中靠最前的任务,并执行当前任务)、DiscardPolicy(直接丢弃任务)。当然我们可以根据自己的需要定义一个拒绝策略,只要实现 RejectedExecutionHandler 接口就可以了。

参数重点讲解

当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
在这里插入图片描述

发布了29 篇原创文章 · 获赞 19 · 访问量 6497

猜你喜欢

转载自blog.csdn.net/wenzhouxiaomayi77/article/details/103493121