执行模型 - 顺序 vs 并发 vs 并行


执行模型 | 顺序 vs 并发 vs 并行


顺序执行

单线程

我们将研究的第一个模型是“单线程顺序模型”。这是最简单的编程风格。每项任务一次执行一项,其中一项任务在开始之前完成。这就像你只有一个工人来完成所有三个任务,如果给了一个任务,工人将完成它直到它终止,然后再拿起任何其他任务。

让我们考虑一个人做饭。他想煮面包煎蛋卷。他开始煮鸡蛋,先等它煮熟,然后开始烤面包,然后吃掉。

执行模型 | 顺序 vs 并发 vs 并行

class SequentialExecutionSingleThreaded {
  public static void main(String[] args) throws InterruptedException {
    long sum = 0;
    for (long i = 0; i <= Integer.MAX_VALUE; i++) {
      sum += i;
    }
    System.out.println("Sum : " + sum);

    // Output 
    //  Sum : 2305843008139952128
  }
}

多线程

另一个模型是多线程顺序模型。在这个模型中,主线程将任务分配给子线程/工作线程,但同时阻塞它们以完成执行,然后再将另一个任务分配给其他工作线程。

用上面做面包煎蛋卷的类比,假设一个人开始煮鸡蛋,煮熟后她妈妈会烤面包。这些任务一个接一个地由不同的人(线程)执行。

执行模型 | 顺序 vs 并发 vs 并行

class SequentialExecutionMultiThreaded {

  static long sum = 0;

  public static void getSum(long startRange, long endRange) {
    System.out.println("Using thread : " + Thread.currentThread().getName());
    for (long i = startRange; i <= endRange; i++) {
      sum += i;
    }
  }

  public static void main(String[] args) throws InterruptedException {

    Thread t1 = new Thread(() -> getSum(0, Integer.MAX_VALUE / 2));
    Thread t2 = new Thread(() -> getSum(Integer.MAX_VALUE / 2 + 1, Integer.MAX_VALUE));

    t1.start();
    t1.join(); // waiting for the result to be computed by blocking main thread

    t2.start();
    t2.join(); // waiting for the result to be computed by blocking main thread

    System.out.println("Sum : " + sum);
    /*
     * Output :
     * Using thread : Thread-0
     * Using thread : Thread-1
     * Sum : 2305843008139952128
     */

  }
}

使用flatMap执行未来组合的类似且更现代的代码:

import java.util.concurrent.*;
import java.util.function.BiFunction;
import java.util.function.Supplier;

class SequentialExecutionMultiThreaded {

  static ExecutorService executor = Executors.newFixedThreadPool(2);

  public static void main(String[] args) {
    getSum(0, 0, Integer.MAX_VALUE / 2) 
        .thenCompose(value -> getSum(value, Integer.MAX_VALUE / 2 + 1, Integer.MAX_VALUE)) 
        .thenAccept(value -> System.out.println("Sum : " + value));

  }

  public static CompletableFuture getSum(long initialValue, int startRange, int endRange) {
    return CompletableFuture.supplyAsync(() -> {
      System.out.println("Using thread : " + Thread.currentThread().getName());
      long sum = 0;
      for (long i = startRange; i <= endRange; i++) {
        sum += i;
      }
      return initialValue + sum;
    }, executor);
  };
}

并发或者交错执行

并发是一次处理很多事情的能力。让我用一个简单的比喻来解释。

让我们考虑一个人在慢跑。在他慢跑的过程中,假设他的鞋带解开了。他停下来系鞋带,然后又开始跑。这是并发的经典示例,因为人们能够同时处理跑步和系鞋带。

单线程

传统上,我们一直使用多个线程一次处理多个任务,每个线程都在自己的单独任务上取得进展。

单线程并发意味着在单个线程内一次处理多个任务。Node.js使这种模式流行起来,因为它可以执行真正的非阻塞 I/O 并以更少的内存占用实现更高的并发性。

执行模型 | 顺序 vs 并发 vs 并行

回到同样的面包煎蛋卷类比,我把鸡蛋煮沸并设置一个计时器,我把面包烤好,然后启动另一个计时器,当它们完成后,我就可以吃了。

现在我们可以看到同步模型和异步模型的含义了。

同步执行意味着启动任务的线程需要等待任务完成才能继续工作,异步执行意味着任务在未来的某个时间点执行,无需等待任何事情完成.

异步并不意味着在另一个线程中。举个例子

List li = new ArrayList<>(Arrays.asList("z","y","x"));
li.sort(Comparator.naturalOrder());

通过提供内置比较器,我们按升序对字符串列表进行排序。问题是

这个比较器什么时候执行?在未来的某个时候。

它在哪个线程中执行?调用它的主线程或同一线程。

异步和并发是两个不同的概念。我们可以异步而不并发,并发并不一定意味着我们是异步的。

多线程

在此模型中,每个任务都在单独的控制线程中执行。在内核级 线程的情况下,线程由操作系统管理,在用户级线程(即绿色线程或协程)的情况下,由语言运行时管理。

执行模型 | 顺序 vs 并发 vs 并行

回到同样的面包煎蛋卷类比,我雇了 2 名厨师,他们会煮鸡蛋并为我烤面包。

他们可以同时做到这一点,而且他们不必等待一个完成才能开始另一个,但他们正在同一个厨房使用一种资源做饭:

class ConcurrentExecutionMultiThreaded {

  static long sum = 0;

  public static void getSum(long startRange, long endRange) {
    System.out.println("Using thread : " + Thread.currentThread().getName());
    for (long i = startRange; i <= endRange; i++) {
      sum += i;
    }
  }

  public static void main(String[] args) throws InterruptedException {

    Thread t1 = new Thread(() -> getSum(0, Integer.MAX_VALUE / 2));
    Thread t2 = new Thread(() -> getSum(Integer.MAX_VALUE / 2 + 1, Integer.MAX_VALUE));

    t1.start(); 
    t2.start();  core, they will run concurrently.

    
    t1.join();
    t2.join();


    System.out.println("Sum : " + sum);
    /*
     * Output :
     * Using thread : Thread-0
     * Using thread : Thread-1
     * Sum : 2305843008139952128
     */

  }
}

使用 future获取执行结果

import java.util.concurrent.*;

class ConcurrentExecutionMultiThreaded {

  static long sum = 0;

  public static long getSum(long startRange, long endRange) {
    System.out.println("Using thread : " + Thread.currentThread().getName());
    long sum = 0;
    for (long i = startRange; i <= endRange; i++) {
      sum += i;
    }
    return sum;
  }

  public static void main(String[] args) throws InterruptedException, ExecutionException {

    ExecutorService service = Executors.newFixedThreadPool(2);
    
    Future f1 = service.submit(() -> getSum(0, Integer.MAX_VALUE / 2));
    Future f2 = service.submit(() -> getSum(Integer.MAX_VALUE / 2 + 1, Integer.MAX_VALUE));

    long s1 = f1.get();
    long s2 = f2.get();

    
    System.out.println("Sum : " + (s1 + s2));
    service.shutdown();
    /*
     * Output :
     * Using thread : pool-1-thread-1
     * Using thread : pool-1-thread-2
     * Sum : 2305843008139952128
     */

  }
}

并行执行

多线程

并行性是同时做很多事情。它可能听起来类似于并发,但实际上是不同的。让我们举一个例子,你接到一个正在和他通话的朋友打来的电话,同时你又接到另一个朋友打来的电话。您将他们俩都安排在电话会议上,现在您正在同时与他们两个人交谈。

为了实现并行性,我们需要硬件支持,在这种情况下,需要多个 CPU 内核。

面包煎蛋卷的类比也是如此,你雇了 2 名厨师,他们会煮鸡蛋并为你烤面包。他们可以同时做到这一点,他们也不必等待一个完成才能开始另一个,但他们现在正在不同的厨房做饭,消耗不同的资源

执行模型 | 顺序 vs 并发 vs 并行

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章