引子

近来把玩了Java 8 的一些特性,如Lambda 表达式Stream,实在过瘾,有初学编程之快感,相见恨晚之情且按下不表。

其中 Stream 有一个parallelStream()的API则着实勾起了我的好奇心,parallel(译过来是“并行”)这个词于我而言可谓陌生,在不算深入的使用和品味一番后,记以本文。本人才疏学浅,如有谬误之处,请不吝指教。

咬文嚼字

在 Android 的 Java 代码世界里并发(concurrency)这个词比较常见,而并行(parallelism)这个词则少之又少,且常常被人与前者混为一谈。实质上,这两个词是完全不同的概念。

援引Java官方的文档中定义(翻译可能不够准确,建议看原文):

parallelism 译为并行,指至少两个线程同一时间在运行

concurrency 译为并发,指存在至少两个线程在执行着任务。更为一般性的并行,以时间分片作为执行单元的虚拟的并行。

我的理解:

首先得跟官方文档一样明确一个词义范围,否则就没有一点“可比性”了,提到这两个词多用其程序上执行“任务”层面的含义。

并行,就是字面意思,同一时刻执行多个任务。指执行程序代码的机器的物理上的一种能力,各个任务如同平行世界般的,形式上可以是多线程乃至多进程,被多个CPU同时运作。任务之间无任何依赖关系,不互斥、不交叉。

并发,指程序逻辑上的一种能力,支持多个任务同时存在一起被处理的能力。一个任务通常被划分为一个个的时间分片,比肩接踵般轮流的、可交替切换的被一个或多个CPU所执行,即不同任务的时间分片之间会有互斥、等待,一个任务可能因为CPU资源被抢走而被中断执行。

举个简单例子,单核CPU的机器,执行多线程化处理过的Java程序,一样称该程序支持并发,但它囿于机器性能永远做不到并行。

所提的并发和并行的概念都是为了最大化的利用多核CPU的能力,从而加速程序的执行。从这点来讲,那些将两个词混为一谈的人多半为一知半解的(看到这里的你已经不是了~)。

简单示例

parallel vs serial

parallel 并行的反义词是serial 串行。

以 Java 的 stream为例:

串行流计算总和

1
2
3
4
int sum = widgets.stream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();

并行流计算总和

1
2
3
4
int sumOfWeights = widgets.parallelStream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();

实质上,并行流与串行流的关键区别在于“分解”二字。

分解任务

如图所示,将计算总和的任务,分解为一系列足够小的计算和任务(如只计算相邻的两个数的值),再将各个子任务执行结果合并到一起作为总结果输出,而且各个子任务可以同时被多个线程所执行。

fork/join

ps. 并行流其实是基于Fork/Join框架实现的,本文就不细说了,对F/J有兴趣的可以看官方示例
pps. “分解”其实就是“动态规划”

以Android 的 AsyncTask 为例:

直接调用AsyncTask.execute()其实为串行 (API >=11),多个AsyncTask存在于一个队列被一个接一个处理

使用指定线程池执行,AsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)则为并行,多个AsyncTask可被指定线程池同时执行(取决于线程池的实现)。

Concurrent vs Sequential

Concurrent 并发(执行)的反义词是 Sequential 顺序(执行)

设有一系列任务:task1…taskN

顺序(执行):线程将task1开始执行到taskN执行结束,task1…taskN执行是连续的。

1
2
3
4
5
6
7
Thread(){
task1.run();
task2.run();
task3.run();
...
taskN.run();
}

并发(执行):将这系列任务提交给一组线程执行(一般为线程池),这组线程之间互相竞争CPU资源,task1…taskN 交替执行结束。

1
2
3
4
5
6
threadPool = newFixedThreadPool(8);
threadPool.submit(task1);
threadPool.submit(task2);
threadPool.submit(task3);
....
threadPool.submit(taskN);

题外话

Android上的java代码写得多了,难免有一种与世界脱轨的感觉,java即将到9,而Android上大部分还只能用1.7的部分特性(API Level 24 才可以使用java 8的部分feature),“与时俱进”是程序猿必修课,希望我还能跟上这“日新月异”的世界。

另请参见

Package java.util.stream Description

Fork and Join: Java Can Excel at Painless Parallel Programming Too!

Java Fork and Join using ForkJoinPool

Concurrency vs. Parallelism

Concurrency vs Parallelism - What is the difference?

Concurrent and Parallel Are Not The Same

https://www.zhihu.com/question/33515481