文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

并发实现:掌握不同并发框架的选择和使用秘诀

2024-11-29 19:33

关注

这些并发框架之间差异很大,若选择或使用不当,容易导致开发出的软件性能较差。并且,现在仍有不少程序员对这些并发框架缺乏系统认识,在选择和使用时较为随意。

接下来,就让我们从最为熟悉的 Thread Pool 框架开始学习吧。

Java Thread Pool 框架

Java 的 Thread Pool 框架目前极为流行,原因在于其使用便捷且适用场景众多。同时,它还是其他并发框架(例如 Akka)内部实现所依赖的技术,所以对这个框架的理解和学习至关重要。那么,为了能够更好地理解 Java Thread Pool 框架,我们先来查看一下它的框架模型图:

图片

从图的左侧开始看,具体实现了接口 Runnable、Callable 的任务,在调用 ExecutorService.submit 接口时,会将任务提交到 ExecutorService 内部的一个任务队列中。

与此同时,ExecutorService 内部还存在一个预先申请的线程池(Thread Pool)。线程池中的线程会从任务队列中领取一个任务来执行。

由此我们能够发现,在 Java 语言中,与直接在代码中创建线程相比,采用 Thread Pool 这种机制有很多好处。

第一个好处是,在 Thread Pool 中,可以重复利用已创建的线程资源,从而减少线程创建和销毁造成的额外开销。第二个好处是,当有新业务请求到达时,可以直接使用已创建的线程来处理业务,所以还可以最大化地减少处理时延。

实际上,对于一个软件系统而言,线程是非常重要的稀缺资源。而线程池技术也是有效管理线程资源、最大化提升软件性能的关键手段之一。

然而,我见过不少的软件系统,在使用线程池时根本没有章法。每个开发人员在自己的业务模块中随意创建线程池。而对于整个软件系统来说,业务代码中一共创建了多少个线程池、每个线程池的资源规模配置如何,都是很含糊的。从而就导致开发出的软件性能总是处于不可控的状态。

所以通常情况下,在使用线程池设计并发系统时,需要对线程池的创建与配置进行全局设计。这里就产生了问题,即应该依据什么规则来划分线程池组?以及如何配置线程池使用的资源呢?实际上,以我的实践经验来看,我认为应该从以下两个维度来划分线程池组。

首先,应根据不同的业务逻辑特点进行划分。例如,可以把以 CPU 计算为主和以 IO 处理为主的业务逻辑划分到不同的线程池组中。其次,还可以根据不同业务功能的优先级来划分出不同的线程池组。

当线程池组的划分确定之后,就可以根据 JVM 中可用的 CPU 核资源数目(可使用 Runtime.getRuntime ().availableProcessors () 获取 JVM 可用的 CPU 核数),为不同的线程池组分配合理的线程资源额度。

然后,在为线程池组配置可用的线程资源时,需要针对不同线程池上的业务特点,选择不同的线程资源配置策略。比如,针对 CPU 计算密集型业务,只需让线程池配置的可用线程数与可分配的 CPU 核数相等即可;而针对 IO 密集型业务,由于业务中的阻塞请求较多,所以可以将配置的线程数提高到可用 CPU 核数的两倍以上。

当然,我这里只是介绍了大体的配置思路,当你在为线程池组配置可用的线程时,最好是基于真实的业务运行特性分析,并从全局统筹分配之后,再为每个线程池配置合适的线程资源。

好的,现在我们再次回顾前面的线程池框架模型图。不知你是否注意到,这个框架模型中并未考虑线程之间的通信机制应如何实现。此时你可能会思考:当业务中的线程之间存在信息交互时,该怎么办呢?这时,你肯定会想到可以基于 Java 并发消息队列进行通信,还可以使用各种同步互斥锁。的确,在 Java 语言中,内置的并发消息队列与互斥锁等机制几乎可以满足线程间的各种同步交互需求,若合理设计并使用,也能很好地发挥软件性能。然而,在真实的业务开发过程中,并发消息队列和锁机制若使用不当,不仅容易导致软件出现严重故障,还容易使系统中的某些线程长时间阻塞,从而不能很好地满足业务的性能需求。另外,并发消息队列和锁在解决同步互斥和数据一致性问题时带来的内部开销也会在一定程度上消耗软件的性能。那么,有没有不需要直接使用并发消息队列和锁就能设计和实现高并发系统的框架呢?答案当然是有,接下来我要为你介绍的 Akka 并发框架,就是为了解决这个问题。

Akka 并发框架

首先我们知道,Akka 是基于 Actor 模型实现的一套并发框架。所以这里,我们同样是先通过一个 Actor 核心模型图,来了解下 Akka 并发框架的特点:

图片

在这个模型图中,每个 Actor 代表着可以被调度执行的轻量单元。如图所示,当 Actor A 和 Actor C 向 Actor B 发送消息时,所有消息会被底层框架发送到 Actor B 的 Mailbox 中。然后,底层的 Akka 框架调度代码会触发 Actor B,使其接收并执行消息的后续处理。

这样一来,基于 Actor 模型的这套并发框架,首先保证了消息能够安全地在各个 Actor 之间传递,同时也确保了每个 Actor 实例可以串行处理接收到的所有消息。

因此,在采用基于 Actor 模型的 Akka 框架开发实现软件时,你无需关注底层的并发交互同步问题,只需聚焦于业务中每个 Actor 实现的业务逻辑,即它需要接收什么消息,又需要向谁发送什么消息。

另外,由于 Actor 模型中的消息机制实现了消息在 Actor 之间传递时会被串行处理,所以天然避免了在消息交互中需要解决的数据一致性问题。也就是说,针对系统中并发单元间存在大量信息交互的场景,选用 Akka 并发框架在性能上会有一定优势。

其实,Actor 模型还有一个更大的优势,那就是 Actor 非常轻量。它可以支持很大规模的并发,并负载均衡到各个 CPU 核上,从而充分发挥硬件资源,进一步提升软件的运行性能。那么接下来,为了更好地理解这个原理,我们来看一个任务拆分示意图。它描述了两种不同的任务拆分方式,以及将拆分的子任务映射到 CPU 具体核上的执行过程。

图片

在图中,左侧的方法 1 代表传统基于线程的粒度并发拆分。可以发现,在这里想要拆分成大小均匀的并发子任务其实很有挑战。当拆分出的子任务大小规模差别较大时,将它们映射到底层 CPU 的核上执行,会造成 CPU 核上的负载不均衡。也就是说,传统的任务拆分方式会出现某些核处于空闲状态,而另外的核上还有线程在执行的场景。在这种情况下,CPU 多核的性能空间就无法发挥到极致。

而图中右侧的方法 2 代表 Actor 的细粒度任务拆分。它可以把业务功能拆分成大量轻量级的 Actor 子任务。由于每个 Actor 都非常轻量,Akka 的底层调度框架就可以将这些 Actor 子任务均匀地分布到多个 CPU 硬件核上,从而最大化地发挥 CPU 的性能。

所以,在实际的业务开发中要注意,如果在使用 Actor 时,没有利用好 Actor 轻量级的特性,开发出来的 Actor 承载的业务逻辑太多,导致 Actor 的任务粒度过大,那么就很难发挥出 Actor 的最佳性能表现。

OK,在理解了这种并发框架的使用优势之后,你可能仍然存在一个问题,就是究竟什么样的业务系统会存在大量的并发信息交互,比较适合采用 Akka 并发框架呢?

按照我的实践经验,一般情况下,CPU 计算密集型的软件系统会比较适合采用 Akka 并发框架。如果发现业务系统中存在大量基于并发消息队列的通信,且核心业务都是围绕着 CPU 计算逻辑,而 IO 请求并非核心业务逻辑,那么这样的系统很可能比较适用 Akka 并发框架。实际上,很多种计算执行引擎就是比较典型的代表。比如,我之前开发的智能对话引擎,需要将多个计算模型的计算结果放在一起进行比较分析,它就非常适合采用 Akka 并发 Actor 框架模型。

不过,对于一些典型的互联网微服务来说,当它们收到 REST 请求后,实现的核心业务逻辑主要是针对数据库 CRUD 或是针对其他服务的 REST 接口调用,同时,这些不同的 REST 请求业务还是相对独立的。那么,这类系统就应该属于 IO 密集型业务,所以选择采用 Akka 并发框架,往往优势不是很大。

那么,针对 IO 密集型业务,选用线程池并发框架是不是就是性能最佳的方案呢?其实也不一定。下面我们就一起看下 Reactor 并发框架的实现特点,并了解下它在解决 IO 密集型业务时存在的优势吧。

Reactor 响应式框架

Reactor 架构是一种基于数据流的响应式架构模式,严格来讲它或许不能算是完整的并发框架,但却内置了灵活调整并发的机制和能力。对于不太熟悉函数式编程范式的程序员而言,理解和使用 Reactor 架构可能会有些挑战。不过没关系,在今天的课程中,我会帮你弄清楚 Reactor 架构模型的基本原理和优势,你不必受限于细节。当你在实际业务中需要决策是否使用这款并发框架时,再选择深入学习具体的用法也不晚。

好,首先,我们一起来看看 Reactor 框架的工作原理图。

图片

如上图所示,输入流 Flux 是 Reactor 中典型的异步消息流,它代表着一个包含 0 个到 N 个的消息序列。另外,图中的 Rule 代表的是一个基于消息的处理逻辑或规则,输入流中的消息可以被中间多个处理逻辑组合连续加工之后,再生成一个包含 0 个到 N 个的输出消息流 Flux。

在看完原理图之后,我们需要思考一个问题:Reactor 为什么要采用这样的计算模型呢?它又能给软件的性能带来什么样的优势呢?其实,这里主要有两个比较明显的优势,接下来我为你重点介绍。

第一个较大的性能优势是它提供了背压机制。通俗来讲,就是图中的中间处理规则(Rule)在接收处理消息时采用的是 Pull 模式,所以不存在数据消息积压的情况。对于传统的分布式并发系统而言,内部消息堆积是一个很普遍的影响性能的因素,而使用 Reactor 框架就可以避免这种情况发生。

第二个较大的性能优势是在中间的消息处理规则实现中,针对 IO 的交互操作可以采用非阻塞的异步交互。在原来传统的基于线程与 IO 交互的实现过程中,不管是使用直接的 IO 请求,还是基于 Future 的 get 机制,都不可避免地会发生当前线程被阻塞的情况。所以基于 Reactor 的异步响应式交互模式,在处理多 IO 请求时性能会更出色。

另外,在 Spring Boot 2.0 版本之后,也提供了对 Reactor 的全面支持,可以支持你去实现事件驱动模型的后端开发,从而更好地发挥软件的性能优势。

来源:二进制跳动内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯