并发和并行,如何区分?如何使用?

开发
本文,我们从多个维度对比了并发和并行,虽然在处理多任务方面它们有共同之处,但它们的目标和实现方式不同。

在计算机科学中,“并发”和“并行性”是两个经常被混淆但实际上具有不同含义的概念。这篇文章,我们将深入探讨这两个概念,并通过Java代码演示它们的实现。

关于并行和并发, 先看一张很形象的 gif图片(图片来自网络):

接着,我们对照这上面的gif图来详细地分析两者。

1. 并发

(1) 定义

并发性(Concurrency)是指系统能够处理多个任务,但不一定是同时执行。关键在于任务的管理,使得多个任务在时间上交错进行,以提高资源利用率和响应能力。

如下图,在一个 CPU上,交替执行多个task:

(2) 特点

  • 任务切换:在单核或多核系统上,通过快速切换任务,让用户感觉任务是同时进行的。
  • 资源共享:多个任务共享系统资源,如CPU、内存等。
  • 异步处理:任务可以在等待某些操作完成(如I/O)时,切换到其他任务。

(3) 实际应用示例

  • 用户界面:在图形用户界面(GUI)中,主线程负责响应用户输入,而后台线程处理耗时操作,使界面保持响应。
  • 服务器处理:Web服务器同时处理多个客户端请求,通过线程池或异步IO管理并发连接。

(4) Java实现示例

以下是一个简单的Java并发示例,模拟多个任务交替执行。

public class ConcurrencyExample {
    public static void main(String[] args) {
        Runnable task1 = () -> {
            for(int i=1; i<=5; i++) {
                System.out.println("Task1 - Count: " + i);
                try { Thread.sleep(100); } catch (InterruptedException e) {}
            }
        };

        Runnable task2 = () -> {
            for(int i=1; i<=5; i++) {
                System.out.println("Task2 - Count: " + i);
                try { Thread.sleep(100); } catch (InterruptedException e) {}
            }
        };

        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task2);

        thread1.start();
        thread2.start();
    }
}

输出示例(任务交错执行):

Task1 - Count: 1
Task2 - Count: 1
Task1 - Count: 2
Task2 - Count: 2
...

在这个例子中,两个任务在同一个处理器上交替执行,实现了并发性。

2. 并行性

(1) 定义

并行性(Parallelism)是指利用多核或多处理器系统同时执行多个任务或任务的多个部分,以加快总体处理速度。

如下图,多个CPU,每个CPU上分别执行一个 task:

(2) 特点

  • 真实的同时执行:在多核处理器上,多个任务可以在不同的核心上同时运行。
  • 任务分解:大的任务可以分解为多个子任务,并行处理后合并结果。
  • 性能提升:通过并行执行,能够显著缩短处理时间,尤其适合计算密集型任务。

(3) 实际应用示例

  • 科学计算:数值模拟、天气预报等需要处理大量数据的应用程序。
  • 大数据处理:Hadoop、Spark等框架通过并行计算提高数据处理速度。

(4) Java实现示例

以下是一个使用Java并行流实现并行计算的示例,计算1到1000000的平方和。

import java.util.stream.LongStream;

public class ParallelismExample {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

        long sum = LongStream.rangeClosed(1, 1_000_000)
                             .parallel()
                             .map(x -> x * x)
                             .sum();

        long endTime = System.currentTimeMillis();
        System.out.println("Sum: " + sum);
        System.out.println("Time taken: " + (endTime - startTime) + " ms");
    }
}

输出示例:

Sum: 333333833333500000
Time taken: 50 ms

在多核处理器上,.parallel()方法使得流操作并行执行,从而加快计算速度。

3. 并发与并行性的对比

(1) 目标

  • 并发性:使多个任务有条不紊地进行,增强系统响应能力和资源利用率。
  • 并行性:通过同时执行多个任务,提升总体处理速度和吞吐量。

(2) 示例对比

  • 并发:单核处理器上,通过时间片轮转执行多个任务,使用户感觉多个任务同时进行。
  • 并行:多核处理器上,多个任务或任务的部分在不同核心上同时执行。

(3) 性能考虑

  • 并发性适用于I/O密集型应用,通过管理任务等待时间提高系统效率。
  • 并行性适用于CPU密集型应用,通过利用多核资源加快计算速度。

(4) 资源利用

  • 并发更关注任务的调度和资源的共享。
  • 并行更关注如何划分任务以充分利用多核资源。

4. 通过并发和并行实现的Java框架

在 Java中,提供了丰富的工具和库来实现并发和并行操作,下面分别举一个例子来展示并发和并行的实际使用。

(1) 线程和Executor框架

线程是实现并发的基本单元,Java通过Thread类和Runnable接口提供了对线程的支持。但直接使用Thread可能导致资源管理困难,因此Java引入了Executor框架,简化线程管理。

示例:使用ExecutorService实现并发

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorConcurrencyExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        Runnable task1 = () -> {
            for(int i=1; i<=5; i++) {
                System.out.println("Task1 - Count: " + i);
                try { Thread.sleep(100); } catch (InterruptedException e) {}
            }
        };

        Runnable task2 = () -> {
            for(int i=1; i<=5; i++) {
                System.out.println("Task2 - Count: " + i);
                try { Thread.sleep(100); } catch (InterruptedException e) {}
            }
        };

        executor.submit(task1);
        executor.submit(task2);

        executor.shutdown();
    }
}

输出示例:

Task1 - Count: 1
Task2 - Count: 1
Task1 - Count: 2
Task2 - Count: 2
...

(2) 并行流(Parallel Streams)

Java 8引入了Streams API,它支持顺序和并行操作,极大简化了并行处理的编程复杂度。通过调用.parallel(),可以轻松将流操作并行化。

示例:并行处理列表

import java.util.Arrays;
import java.util.List;

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

        numbers.parallelStream()
               .map(n -> {
                   System.out.println("Processing " + n + " in " + Thread.currentThread().getName());
                   return n * n;
               })
               .forEach(result -> System.out.println("Result: " + result));
    }
}

输出示例(线程顺序可能不同):

Processing 2 in ForkJoinPool.commonPool-worker-1
Result: 4
Processing 1 in ForkJoinPool.commonPool-worker-3
Result: 1
...

5. 实践建议

  • 选择合适的并发工具:对于简单的线程管理,可以使用ExecutorService;对于复杂的任务调度,考虑使用ForkJoinPool。
  • 避免共享可变状态:共享状态可能导致竞态条件(Race Conditions),使用线程安全的数据结构或同步机制。
  • 理解任务的性质:I/O密集型任务适合并发处理,CPU密集型任务适合并行处理。
  • 合理划分任务:避免过度划分导致线程切换开销过大,或任务粒度过粗导致资源浪费。
  • 使用高层次抽象:如Java 8的CompletableFuture,简化异步编程模型。

6. 总结

本文,我们从多个维度对比了并发和并行,虽然在处理多任务方面它们有共同之处,但它们的目标和实现方式不同。并发性侧重于任务的管理和调度,以提高系统的响应能力和资源利用率;而并行性则侧重于通过同时执行多个任务或任务的多个部分,以提升处理速度和吞吐量。

  • 并发性关注的是如何结构化程序以处理多个任务的进展,不一定同时执行。
  • 并行性关注的是如何同时执行多个任务,以加快总体的处理速度。
责任编辑:赵宁宁 来源: 猿java
相关推荐

2012-09-12 15:30:19

分布式集群

2024-09-03 15:14:42

2009-08-04 14:48:26

并发和并行的区别

2021-03-12 18:25:09

开发前端React

2022-04-26 08:41:38

Swift并发系统iOS

2022-01-17 15:43:04

Go 并行性并发性

2015-05-19 16:21:05

2023-01-13 11:27:13

战略平台Forrester

2017-03-18 16:28:40

人工智能机器学习深度学习

2021-02-25 22:17:19

开发技术编程

2024-07-25 14:52:22

2010-02-24 10:24:10

Python线程

2023-12-21 08:51:37

防抖节流Vue.js

2020-09-18 15:10:51

Web前端技术

2018-08-17 10:22:38

监控专用硬盘

2014-04-09 09:32:24

Go并发

2019-07-11 10:00:29

串行并行并发

2009-05-26 15:43:06

山寨版运维管理摩卡软件

2024-11-14 09:59:23

2023-10-13 12:28:38

点赞
收藏

51CTO技术栈公众号