架构师之路-线程池学习

为何需要线程池

线程的创建和销毁都需要映射到操作系统,因此其代价是比较高昂的。出于避免频繁创建、销毁线程以及方便线程管理的需要,线程池应运而生。

线程池优势

降低资源消耗: 线程池通常会维护一些线程(数量为 corePoolSize),这些线程被重复使用来执行不同的任务,任务完成后不会销毁。在待处理任务量很大的时候,通过对线程资源的复用,避免了线程的频繁创建与销毁,从而降低了系统资源消耗。
提高响应速度: 由于线程池维护了一批 alive 状态的线程,当任务到达时,不需要再创建线程,而是直接由这些线程去执行任务,从而减少了任务的等待时间。
提高线程的可管理性: 使用线程池可以对线程进行统一的分配,调优和监控。

线程池设计思路

线程池流程图:
在这里插入图片描述
线程池流程简图:
在这里插入图片描述

线程池源码

ThreadPoolExecutor构造方法中涉及到的参数:

  • corePoolSize(必需): 核心线程数。即池中一直保持存活的线程数,即使这些线程处于空闲。但是将allowCoreThreadTimeOut参数设置为true后,核心线程处于空闲一段时间以上,也会被回收。
  • maximumPoolSize(必需): 池中允许的最大线程数。当核心线程全部繁忙且任务队列打满之后,线程池会临时追加线程,直到总线程数达到maximumPoolSize这个上限。
  • keepAliveTime(必需): 线程空闲超时时间。当非核心线程处于空闲状态的时间超过这个时间后,该线程将被回收。将allowCoreThreadTimeOut参数设置为true后,核心线程也会被回收。
  • unit(必需): keepAliveTime参数的时间单位。有:TimeUnit.DAYS(天)、TimeUnit.HOURS(小时)、TimeUnit.MINUTES(分钟)、TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)、TimeUnit.MICROSECONDS(微秒)、TimeUnit.NANOSECONDS(纳秒)
  • workQueue(必需): 任务队列,采用阻塞队列实现。当核心线程全部繁忙时,后续由execute方法提交的Runnable将存放在任务队列中,等待被线程处理。
  • threadFactory(可选): 线程工厂。指定线程池创建线程的方式。
  • handler(可选): 拒绝策略。当线程池中线程数达到maximumPoolSize且workQueue打满时,后续提交的任务将被拒绝,handler可以指定用什么方式拒绝任务。

workQueue任务队列

使用ThreadPoolExecutor需要指定一个实现了BlockingQueue接口的任务等待队列。在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueue、LinkedBlockingQueue和ArrayBlockingQueue

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。在线程池初始化时,指定队列的容量,后续无法再调整。这种有界队列有利于防止资源耗尽,但可能更难调整和控制。
    new ThreadPoolExecutor(1,10,500L, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(10));

  2. LinkedBlockingQueue:无界队列(严格来说并非无界,上限是Integer.MAX_VALUE),基于链表结构。使用无界队列后,当核心线程都繁忙时,后续任务可以无限加入队列,因此线程池中线程数不会超过核心线程数。这种队列可以提高线程池吞吐量,但代价是牺牲内存空间,甚至会导致内存溢出。另外,使用它时可以指定容量,这样它也就是一种有界队列了(一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE)。
    new ThreadPoolExecutor(1,10,500L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>());

  3. SynchronousQueue:同步队列。这是一个内部没有任何容量的阻塞队列,一个不存储元素的阻塞队列,消费者线程调用take。
    new ThreadPoolExecutor(1,10,500L, TimeUnit.MILLISECONDS,new SynchronousQueue<>());//不存储元素实时的

Java还提供了另外4种队列:

  1. LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
    new ThreadPoolExecutor(1,10,500L, TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>());// 有界
  2. PriorityBlockingQueue: 一个支持优先级排序的 无界阻塞队列,对元素没有要求,可以实现Comparable接口也可以提供Comparator来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
    new ThreadPoolExecutor(1,10,500L, TimeUnit.MILLISECONDS,new PriorityBlockingQueue<>());// 无界
  3. DelayQueue: 延迟队列。类似于PriorityBlockingQueue,是二叉堆实现的 无界 优先级阻塞队列。必须是实现Delayed接口的类对象,通过执行时延从队列中提取任务,时间没到任务取不出来。
    new ThreadPoolExecutor(1,10,500L, TimeUnit.MILLISECONDS,new DelayQueue());// 无界
  4. LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体:
    由链表结构组成的无界阻塞队列。这个队列比较特别的时,采用一种预占模式,意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素。但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。
    new ThreadPoolExecutor(1,10,500L, TimeUnit.MILLISECONDS,new LinkedTransferQueue<>());

注意有界队列和无界队列的区别:
如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;
而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。

handler拒绝策略

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。(默认)
  • ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃
  • ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新提交被拒绝的任务。
  • ThreadPoolExecutor.CallerRunsPolicy : 直接运行这个任务的run方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理。

Executors

Executors 的4个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

其实 Executors的4个功能线程有如下弊端:

  • FixedThreadPool (new LinkedBlockingQueue())
    SingleThreadExecutor (new LinkedBlockingQueue())
    主要问题是堆积的请求处理队列均采用 LinkedBlockingQueue ,可能会耗费非常大的内存,甚至 OOM。
    CachedThreadPool (new SynchronousQueue())
    ScheduledThreadPool (new DelayedWorkQueue())
    主要问题是 线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。

线程池状态

runState表示当前线程池的状态,它是一个 volatile 变量用来保证线程之间的可见性。
下面的几个static final变量表示runState可能的几个取值,有以下几个状态:

  • RUNNING:当创建线程池后,初始时,线程池处于RUNNING状态;
  • SHUTDOWN:如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
  • STOP:如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
  • TERMINATED:当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

初始化&容量调整&关闭

  1. 线程初始化
    默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
    在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
  • prestartCoreThread():boolean prestartCoreThread(),初始化一个核心线程
  • prestartAllCoreThreads():int prestartAllCoreThreads(),初始化所有核心线程,并返回初始化的线程数
  1. 线程池关闭
    ThreadPoolExecutor提供了两个方法,用于线程池的关闭:
  • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
  1. 线程池容量调整
    ThreadPoolExecutor提供了动态调整线程池容量大小的方法
  • setCorePoolSize:设置核心池大小
  • setMaximumPoolSize:设置线程池最大能创建的线程数目大小

当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值