Java 线程池原理

一 概述

随着计算机行业的飞速发展,摩尔定律逐渐失效,多核 CPU 成为主流。使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器。J.U.C 提供的线程池 ThreadPoolExecutor 类,帮助开发人员管理线程并方便地执行并行任务。了解并合理使用线程池,是一个开发人员必修的基本功。

本文开篇简述了线程池概念和用途,接着结合线程池的源码,帮助大家领略线程池的设计思路,最后回归实践,通过案例讲述使用线程池遇到的问题,并给出了一种动态化线程池解决方案。

1.1 线程池是什么

线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如 MySQL。

线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。

而本文描述线程池是 JDK 中提供的 ThreadPoolExecutor 类。

使用线程池带来的一系列好处:

  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控
  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行

1.2 线程池解决什么问题

线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:

  • 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大
  • 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险
  • 系统无法合理管理内部的资源分布,会降低系统的稳定性

为解决资源分配这个问题,线程池采用了“池化”(Pooling)思想。池化,顾名思义,是为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。

二 线程池核心设计与实现

在前文中,我们了解到:线程池是一种通过“池化”思想,帮助我们管理线程而获取并发性的工具,在 Java 中的体现是 ThreadPoolExecutor 类。那么它的的详细设计与实现是什么样的呢?我们接下来详细介绍。

2.1 总体设计

Java 中的线程池核心实现类是 ThreadPoolExecutor,本章基于 JDK 1.8 的源码来分析 Java 线程池的核心设计与实现。我们首先来看一下 ThreadPoolExecutor 的 UML 类图,了解下 ThreadPoolExecutor 的继承关系。
在这里插入图片描述
ThreadPoolExecutor 实现的顶层接口是 Executor,顶层接口 Executor 提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供 Runnable 对象,将任务的运行逻辑提交到执行器 (Executor) 中,由 Executor 框架完成线程的调配和任务的执行部分。

ExecutorService 接口增加了一些能力:

  • 扩充执行任务的能力,补充可以为一个或一批异步任务生成 Future 的方法
  • 提供了管控线程池的方法,比如停止线程池的运行

AbstractExecutorService 则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。实现类 ThreadPoolExecutor 实现最复杂的运行部分,ThreadPoolExecutor 将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。

ThreadPoolExecutor 是如何运行,如何同时维护线程和执行任务的呢?其运行机制如下图所示:
在这里插入图片描述
线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管理、线程管理。任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:

  • 直接申请线程执行该任务
  • 缓冲到队列中等待线程执行
  • 拒绝该任务

线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。

接下来,我们会按照以下三个部分去详细讲解线程池运行机制:

  • 线程池如何维护自身状态
  • 线程池如何管理任务
  • 线程池如何管理线程

2.2 生命周期管理

线程池运行的状态,并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护的。线程池内部使用一个变量维护两个值:运行状态 (runState) 和线程数量 (workerCount)。在具体实现中,线程池将运行状态 (runState)、线程数量 (workerCount) 两个关键参数的维护放在了一起,如下代码所示:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctl 这个 AtomicInteger 类型,是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它同时包含两部分的信息:线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),高3位保存 runState,低 29 位保存 workerCount,两个变量之间互不干扰

用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。通过阅读线程池源代码也可以发现,经常出现要同时判断线程池运行状态和线程数量的情况。线程池也提供了若干方法去供用户获得线程池当前的运行状态、线程个数。这里都使用的是位运算的方式,相比于基本运算,速度也会快很多。

关于内部封装的获取生命周期状态、获取线程池线程数量的计算方法如下:

private static int runStateOf
Java并发编程中,线程池是一个非常重要的概念。它通过复用预先创建好的一组工作线程来执行提交的任务,以此减少频繁创建销毁线程所带来的开销,并且可以有效地控制最大并发数,提高应用程序的整体性能。 ### Java 线程池的基本组成元素 1. **核心线程数 (corePoolSize)** - 当新任务到来时,首先会尝试分配给空闲的核心线程处理;如果所有核心线程都在忙,则进入下一步骤。 2. **最大线程数 (maximumPoolSize)** - 表示允许的最大活动线程数目。一旦达到此上限值,后续到达的任务将暂时放入阻塞队列等待资源释放后再被执行。 3. **保持时间 (keepAliveTime)** - 对于超出核心线程以外的工作线程来说,在没有新任务的情况下持续存在的最长时间之后就会被回收掉以节省系统资源。 4. **任务队列 (workQueue)** - 用于保存待处理任务的数据结构,通常是指定长度的BlockingQueue实现类。例如: * `LinkedBlockingQueue`: 可选容量大小,默认无界队列; * `SynchronousQueue`: 特殊情况下的直接传递型队列,每次put操作都需要有一个take操作与之配对发生才能成功完成入队动作; * 其他定制化的缓存策略也可以根据需求自行设计实现。 5. **拒绝策略 (RejectedExecutionHandler)** - 当线程池关闭或者是当前负载过高以至于不能再接受新的请求时所采取的动作。四种内置的方式分别是抛出异常、丢弃旧任务、抛弃最新加入者或是让调用方自己跑起来该次作业等。 6. **线程工厂(ThreadFactory)** – 负责制造新的Thread实体对象的过程封装体,默认行为只是简单地new Thread()而已但用户完全可以按照自身偏好替换成更为复杂的构造形式以便更好地管理和跟踪各个子单元的状态变化趋势。 7. **Callable 和 Future** - Callable 接口类似于 Runnable ,但它可以返回结果并能抛出受检异常(FutureTask),Future 提供了一种获取异步计算结果的方法。 ### 工作流程简介 当向ThreadPoolExecutor提交一个任务时,大致经历以下几个步骤: - 如果正在运行的线程少于 corePoolSize ,即使其他工人也有闲置,仍会优先启动新的工作者来承担此项差事直到数量达标为止; - 若已经有足够的基本成员在线且仍有未决事项,则将其追加到指定容器内排队等候调度安排; - 再次检查是否超过 maximumPoolSize 的极限约束条件,若是则激活 RejectedExecutionHandler 进行相应处置措施避免死锁现象的发生; - 最终由可用的线程取出队首位置处的任务开始正式运作直至结束整个生命周期。 ### 总结 合理利用好Java中的线程池机制可以帮助开发者写出高效稳定的服务端程序。然而实际编码过程中还需要结合具体情况综合权衡各方面的因素选取合适的参数设置组合方案,这样才能真正发挥其优势特性最大化提升整体效能水平。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值