ToB企服应用市场:ToB评测及商务社交产业平台

标题: 深入理解并发和并行 [打印本页]

作者: 写过一篇    时间: 2024-5-17 05:40
标题: 深入理解并发和并行
1 并发与并行

为什么操纵系统上可以同时运行多个程序而用户感觉不出来?
因为操纵系统营造出了可以同时运行多个程序的假象,通过调治进程以及快速切换CPU上下文,每个进程执行一会就停下来,切换到下个被调治到的进程上,这种切换速率非常快,人无法感知到,从而产生了多个任务同时运行的错觉。
并发(concurrent) 是指的在宏观上多个程序或任务在同时运行,而在微观上这些程序交替执行,可以提高系统的资源使用率和吞吐量。
通常一个CPU内核在一个时间片只能执行一个线程(某些CPU采用超线程技术,物理核心数和逻辑核心数形成一个 1:2 的关系,比如4核CPU,逻辑处理器会有8个,可以同时跑8个线程),假如N个内核同时执行N个线程,就叫做并行(parallel)。我们编写的多线程代码具备并发特性,而不愿定会并行。因为可否并行取决于操纵系统的调治,程序员无法控制,但是调治算法会尽量让差别线程使用差别的CPU核心,所以在实际使用中几乎总是会并行。假如多个任务在一个内核中顺序执行,就是串行(Serial),如下图所示:



并发是多个程序在一段时间内同时执行的现象,而并行是多个任务在同一时刻同时执行,也是多核CPU的紧张特性。
这里有一个疑问:并发肯定并行吗?
并发并不愿定并行。并发是逻辑上的同时发生,而并行是物理上的同时发生。并发可以跑在一个处理器上通过期间片进行切换,而并行须要两个或两个以上的线程跑在差别的处理器上。假如同一个任务的多个线程始终运行在不变的CPU核心上,那就不是并行。
举一个生活中的例子:
2 多核调治算法

在多核CPU系统中,调治算法的主要目的是有效地使用全部可用的CPU核心,以提高系统的整体性能和资源使用率。下面是一些常见的多核CPU调治算法:
这些调治算法可以根据系统的需求进行组合和调整,以实现对多核CPU系统资源的有效管理和使用。
抢占式调治(Preemptive Scheduling)的使用最为广泛,它允许操纵系统在任何时候停止当前正在执行的任务,并将处理器分配给其他任务。这种调治策略使得操纵系统能够实时响应各种事件和哀求,从而提高系统的响应性和实时性。
在抢占式调治中,每个任务都被赋予一个优先级,操纵系统会根据任务的优先级来决定哪个任务应该在当前时间片执行。假如某个高优先级任务准备停当而且当前正在执行的任务的优先级低于它,操纵系统会停止当前任务的执行,并将处理器分配给高优先级任务,从而实现抢占。抢占式调治的主要优点包括:
抢占式调治也存在一些挑战和限制:
抢占式调治在许多操纵系统中得到了广泛应用,包括Windows、Linux、MacOS等,它为实时系统和响应式系统提供了一种高效的任务调治机制。
3 Java并行编程

在编码层面上看,采用Java语言创建多线程代码,不须要程序员打上并行的标记,因为为了充分使用计算资源,操纵系统肯定会尽可能调治多线程到差别的CPU核心上。并发的任务通常有多线程竞争资源和频仍的CPU上下文切换,这些都会降低执行效率。
在实际的业务场景里,许多计算任务实在互不干扰,最后汇总效果就可以了,比如统计差别用户的每日运动次数。它们不存在竞争资源,并行处理的效率非常高,Java语言提供了多线程并行执行的 API。
3.1 Future

在Java并发编程中,Future是一种用于表现异步计算效果的接口。它允许你提交一个任务而且在未来的某个时候获取任务的效果。Future的原理是通过一个占位符来表现异步操纵的效果,在任务完成之前,可以通过Future对象获取占位符,而且在须要的时候等待任务的完成并获取效果。Future接口界说了异步计算效果的标准,具体的异步计算由实现了Future接口的类来执行,比如ExecutorService的submit方法会返回一个Future对象,用于跟踪任务的执行状态和效果。
Future提供了以下主要方法:
看看下面这个代码示例:
  1. import java.util.concurrent.Callable;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.Future;
  5. public class FutureParallelExample {
  6.     public static void main(String[] args) throws Exception {
  7.         ExecutorService executor = Executors.newFixedThreadPool(2);
  8.         Callable<Integer> task1 = () -> {
  9.             // 模拟耗时计算
  10.             Thread.sleep(2000);
  11.             return 10;
  12.         };
  13.         Callable<Integer> task2 = () -> {
  14.             // 模拟耗时计算
  15.             Thread.sleep(3000);
  16.             return 20;
  17.         };
  18.         Future<Integer> future1 = executor.submit(task1);
  19.         Future<Integer> future2 = executor.submit(task2);
  20.         // 异步执行,继续执行下面的代码
  21.         System.out.println("Asynchronous computation is executing.");
  22.         // 获取第一个任务的结果
  23.         Integer result1 = future1.get(); // 这将会阻塞直到任务1完成
  24.         System.out.println("Task 1 result: " + result1);
  25.         // 获取第二个任务的结果
  26.         Integer result2 = future2.get(); // 这将会阻塞直到任务2完成
  27.         System.out.println("Task 2 result: " + result2);
  28.         // 关闭ExecutorService
  29.         executor.shutdown();
  30.     }
  31. }
复制代码
在这个例子中,启动了两个异步任务,并分别获取了它们的 Future 对象。通过 Future.get() 方法,我们可以等待任务完成并获取效果。ExecutorService 使用了一个固定的线程池,大小为2。这意味着两个任务将会并行执行。
3.2 Fork / Join

Fork / Join 框架是Java 7中新增的并发编程工具,主要有两个步骤,第一是fork:将一个大任务分成许多个小任务;第二是 join:将第一个任务的效果 join 起来,生成最后的效果。假如第一步中并没有任何返回值,join将会等到全部的小任务都结束。
斐波那契数列由意大利数学家斐波那契初次提出,这个数列从第三项开始,每一项都等于前两项之和,通常以递归方式界说,即F(0)=1,F(1)=1,对于n>=2的任何正整数n,F(n)=F(n-1)+F(n-2),数列的前几个数字是1,1,2,3,5,8,13,21,34。我们尝试使用递归计算第n项的数值,代码如下:
  1. /**
  2. * 递归实现斐波那契数列
  3. **/
  4. public class FibonacciRecursion {
  5.     public static int fibonacciRecursive(int n) {
  6.         if (n <= 1)
  7.             return n;
  8.         return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
  9.     }
  10.     public static void main(String[] args) {
  11.         int n = 10;
  12.         System.out.println("Fibonacci of " + n + " is " + fibonacciRecursive(n));
  13.     }
  14. }
复制代码
上面代码调用了 parallel() 后,reduce()方法内部逻辑发生了变革,它会根据当火线程池资源分配任务,并行地在差别的工作线程上执行累加操纵,而不是串行执行的。
Java 并行流是基于 Fork/Join 框架实现的,它使用了多线程来处理流操纵。在多核环境中,Fork/Join 框架会根据系统资源自动调整任务分配,尽可能多地使用空闲核心,更充分地发挥硬件潜力。比如,当CPU具有8个内核时,并行计算的耗时远小于串行计算耗时的8倍,但是由于线程创建、销毁以及上下文切换等开销,实际性能提升并非线性。
并行计算并不总是适用于全部场景,特别是在数据集较小或者任务分解后产生的子任务粒度较小时,线程管理的开销可能凌驾并行计算带来的上风。假如硬件只有单核或少核,则并行计算效果有限乃至可能会因线程切换而降低效率。综合考虑以下因素:
3.4 CompletableFuture

CompletableFuture是一个实现了Future接口的类,它提供了一种更加机动和强大的方式来进行异步编程。CompletableFuture可以用来表现一个异步计算的效果,而且提供了丰富的方法来处理异步操纵的完成、组合多个异步操纵、处理非常等。CompletableFuture相比于传统的Future接口,具有以下上风:
我们看看简单的代码示例:
  1. import java.util.concurrent.ForkJoinPool;
  2. import java.util.concurrent.RecursiveTask;
  3. /**
  4. * fork/join实现斐波那契数列
  5. **/
  6. public class FibonacciFork extends RecursiveTask<Integer> {
  7.     final int n;
  8.     public FibonacciFork(int n) {
  9.         this.n = n;
  10.     }
  11.     @Override
  12.     protected Integer compute() {
  13.         if (n <= 1)
  14.             return n;
  15.         FibonacciFork f1 = new FibonacciFork(n - 1);
  16.         FibonacciFork f2 = new FibonacciFork(n - 2);
  17.         f1.fork();
  18.         return f2.compute() + f1.join();
  19.     }
  20.     public static void main(String[] args) {
  21.         ForkJoinPool pool = new ForkJoinPool();
  22.         FibonacciFork fib = new FibonacciFork(10);
  23.         Integer result = pool.invoke(fib);
  24.         System.out.println(result);
  25.     }
  26. }
复制代码
  1. public class StreamDemo {
  2.     public static void main(String[] args) {
  3.         Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
  4.               .reduce((a, b) -> a + b)
  5.               .ifPresent(System.out::println); // 输出结果:45
  6.     }
  7. }
复制代码
  1. public class StreamParallelDemo {
  2.     public static void main(String[] args) {
  3.         Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
  4.               .parallel()
  5.               .reduce((a, b) -> a + b)
  6.               .ifPresent(System.out::println);
  7.     }
  8. }
复制代码
  1. import java.util.concurrent.CompletableFuture;
  2. public class CompletableFutureExample {
  3.     public static void main(String[] args) {
  4.         CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
  5.             // 异步任务,返回结果为100
  6.             return 100;
  7.         });
  8.         // 在任务完成后输出结果
  9.         future.thenAccept(result -> System.out.println("异步任务结果为:" + result));
  10.     }
  11. }
复制代码
这些示例展示了使用CompletableFuture进行异步编程的一些常见用法,包括简单的异步任务、组合多个CompletableFuture、处理非常环境等。总的来说,CompletableFuture是Java并发编程中一个强大而机动的工具,它使得异步编程变得更加简单、清楚和可控。
4 总结

要更好地掌握Java并发编程技能,可以采取以下几个步骤:
参考

https://zhuanlan.zhihu.com/p/622768247
https://www.cnblogs.com/badaoliumangqizhi/p/17021500.html
https://blog.csdn.net/weixin_44073836/article/details/123346035
https://blog.csdn.net/m0_71149992/article/details/125327370
https://www.php.cn/faq/530720.html
https://zhuanlan.zhihu.com/p/424501870
https://zhuanlan.zhihu.com/p/339472446
https://zhuanlan.zhihu.com/p/622218563

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4