【连载01】并发与并行

科技   2024-11-25 08:00   河北  

本章将会介绍Java多线程并发编程的入门知识,从Java多线程常用实现开始,由浅入深了解Java两种常用的线程池创建使用及其适用场景。通过对java.util.concurrent.ThreadPoolExecutor源码的解析,了解自定义Java线程池的几个重要参数,并掌握线程池内在的执行逻辑,达到自定义Java线程池的目的。

并发与并行

在进行Java多线程编程之前,首先分享一个这个概念:并发和并行。

这两个概念在实际工作很少去刻意区分,属于非常基础的知识。如果你想了解Java多线程编程,就必需先搞清楚这两个概念以及差异。

并发(Concurrency)和并行(Parallelism)都是指同时处理多个事务的能力,但是这两个概念本质上还是有差异的。总结来说并行指的是时间上的同时发生,而并发并不一定是。如果站在观察者角度来看,并发看起来很像并行。

要想更好地理解,小故事是无法避免的,下面请让听一听我这个“超市结账”版本。

这里有一家叫“小八”的超市,里面只有一个收银台,但是确有两个收银通道。平时空闲的时候只开放一条收银通道,人多的时候开发两条收银通道,让所有顾客更快完成结账付款,减少等待时间。

但实际情况是这样的,只有一位收银员,但是收银台对于顾客是黑盒,顾客完全无法了解收银台里面如何运行,更无法知道真相:只有一位收银员。

对于单个顾客,他们结账流程是:1. 把商品挨个扫描计价;2. 计算顾客应付金额;3. 顾客付款;4. 顾客收拾商品结束购物。

当空闲的时候,每个顾客结账过程大约需要1分钟。当繁忙的时候,开放两条通道,平均每个顾客结账也需要1分钟。乍一看,收银台的性能提升了1倍。

实际情况是这样的:这位收银员,一边等待第一条通道顾客出示付款码,一边给第二条通道的顾客扫描计价;一边等待第一条通道的顾客自己打包商品,一边再给第二条通道的顾客找零钱。如图1-1所示:

图 1-1 并发收银台

这个小故事里面,超市相当于我们的计算机,收银台或者收银员相当于CPU。当我们只有一颗CPU时,依然可以同时处理两条结账通道的顾客。这里的通道相当于线程。原来1分钟只能完成一个结账周期,通过增加结账通道提升了1倍的性能。

如果你站在顾客的视角,两条通道顾客在同时结账,这个就叫做并发。如果超市老板又招聘了一位收银员,两位收银员分别处理两条结账通道,两条通道相互不影响,这个就叫做并行。如图1-2所示:

图 1-2 并行收银台

对于CPU来说,程序就相当于是超市结账的顾客,所以在使用Java进行性能测试中,我们关心更多的就是并发。

并发和并行是计算机科学中两个密切相关但概念上不同的术语,主要用于描述任务的执行方式。

特征总结:

特性并发(Concurrency)并行(Parallelism)
定义多个任务交替执行,体现为任务之间的协作与调度,侧重任务切换多个任务同时执行,强调同时性,利用多核或多处理器资源。
执行单位任务可以在单核或多核上通过时间片轮转执行(分时共享)。任务必须在多核、多线程或多处理器上同时执行。
特点- 更注重任务的逻辑结构(任务可以部分完成)。 
- 强调程序的设计能力,避免竞态条件。
- 强调硬件能力,要求硬件支持同时运行。 
- 提升任务吞吐量。
典型场景- 多任务处理:如在 GUI 中,UI 响应用户交互的同时处理后台数据更新。 
- 异步 I/O 操作。
- 科学计算:大规模矩阵计算、图像处理等。 
- 并行数据处理,如 MapReduce、GPU 运算。
硬件要求不需要依赖多核,多线程环境即可实现。需要依赖多核、多处理器或 GPU。
技术示例- Java 的线程池(如 Executor)。 
- Golang 的 Goroutines(协程)。
- CUDA 的 GPU 编程。 
- OpenMP 多线程计算。
关键问题如何设计任务的交替逻辑,避免死锁、资源竞争。如何分配任务到多个计算单元,最大化硬件利用率。
FunTester
FunTester 原创精华


FunTester
万粉千文|百无一用
 最新文章