美文网首页
Java 中的并行处理

Java 中的并行处理

作者: 张云飞Vir | 来源:发表于2021-07-13 15:35 被阅读0次

1. 背景

本文是一个短文章,介绍Java 中的并行处理。

说明:10多分钟读完的文章我称之为短文章,适合快速阅读。

2.知识

并行计算(parallel computing)一般是指许多指令得以同时进行的计算模式。在同时进行的前提下,可以将计算的过程分解成小部分,之后以并发方式来加以解决。

也就是分解为几个过程:

  • 1、将一个大任务拆分成多个子任务,子任务还可以继续拆分。
  • 2、各个子任务同时进行运算执行。
  • 3、在执行完毕后,可能会有个 " 归纳 " 的任务,比如 求和,求平均等。

再简化一点的理解就是: 先拆分 --> 在同时进行计算 --> 最后“归纳”

为什么要“并行”,优点呢?

  • 1、为了获得 “节省时间”,“快”。适合用于大规模运算的场景。从理论上讲,在 n 个并行处理的执行速度可能会是在单一处理机上执行的速度的 n 倍。
  • 2、以前的计算机是单核的,现代的计算机Cpu都是多核的,服务器甚至都是多Cpu的,并行计算可以充分利用硬件的性能。

3. Java 中的并行处理

JDK 8 新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中,可以方便开发者能够写出更加有效、更加简洁的代码。

steam 的另一个价值是创造性地支持并行处理(parallel processing)。示例:

final Collection< Task > tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 ) 
);

// 并行执行多个任务,并 求和
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints ) 
   .reduce( 0, Integer::sum );
 
System.out.println( "Total points (all tasks): " + totalPoints );

对于上面的tasks集合,上面的代码计算所有任务的点数之和。
它使用 parallel 方法并行处理所有的task,并使用 reduce 方法计算最终的结果。

4. 扩展

线程池方式实现并行处理

jdk1.5引入了并发包,其中包括了ThreadPoolExecutor,相关代码如下:

public class ExecutorServiceTest {
 
    public static final int THRESHOLD = 10_000;
    public static long[] numbers;
 
    public static void main(String[] args) throws Exception {
        numbers = LongStream.rangeClosed(1, 10_000_000).toArray();
        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);
        CompletionService<Long> completionService = new ExecutorCompletionService<Long>(executor);
        int taskSize = (int) (numbers.length / THRESHOLD);
        for (int i = 1; i <= taskSize; i++) {
            final int key = i;
            completionService.submit(new Callable<Long>() {
 
                @Override
                public Long call() throws Exception {
                    return sum((key - 1) * THRESHOLD, key * THRESHOLD);
                }
            });
        }
        long sumValue = 0;
        for (int i = 0; i < taskSize; i++) {
            sumValue += completionService.take().get();
        }
        // 所有任务已经完成,关闭线程池
        System.out.println("sumValue = " + sumValue);
        executor.shutdown();
    }
 
    private static long sum(int start, int end) {
        long sum = 0;
        for (int i = start; i < end; i++) {
            sum += numbers[i];
        }
        return sum;
    }
}
复制代码

使用 fork/join框架

分支/合并框架的目的是以递归的方式将可以并行的认为拆分成更小的任务,然后将每个子任务的结果合并起来生成整体结果;相关代码如下:

public class ForkJoinTest extends java.util.concurrent.RecursiveTask<Long> {
    
    private static final long serialVersionUID = 1L;
    private final long[] numbers;
    private final int start;
    private final int end;
    public static final long THRESHOLD = 10_000;
 
    public ForkJoinTest(long[] numbers) {
        this(numbers, 0, numbers.length);
    }
 
    private ForkJoinTest(long[] numbers, int start, int end) {
        this.numbers = numbers;
        this.start = start;
        this.end = end;
    }
 
    @Override
    protected Long compute() {
        int length = end - start;
        if (length <= THRESHOLD) {
            return computeSequentially();
        }
        ForkJoinTest leftTask = new ForkJoinTest(numbers, start, start + length / 2);
        leftTask.fork();
        ForkJoinTest rightTask = new ForkJoinTest(numbers, start + length / 2, end);
        Long rightResult = rightTask.compute();
        // 注:join方法会阻塞,因此有必要在两个子任务的计算都开始之后才执行join方法
        Long leftResult = leftTask.join();
        return leftResult + rightResult;
    }
 
    private long computeSequentially() {
        long sum = 0;
        for (int i = start; i < end; i++) {
            sum += numbers[i];
        }
        return sum;
    }
 
    public static void main(String[] args) {
        System.out.println(forkJoinSum(10_000_000));
    }
 
    public static long forkJoinSum(long n) {
        long[] numbers = LongStream.rangeClosed(1, n).toArray();
        ForkJoinTask<Long> task = new ForkJoinTest(numbers);
        return new ForkJoinPool().invoke(task);
    }
}

上面的代码实现了 递归方式拆分子任务,并放入到线程池中执行。

5.参考:

https://zh.wikipedia.org/wiki/%E5%B9%B6%E8%A1%8C%E8%AE%A1%E7%AE%97

END

相关文章

  • Java 中的并行处理

    1. 背景 本文是一个短文章,介绍Java 中的并行处理。 说明:10多分钟读完的文章我称之为短文章,适合快速阅读...

  • Java 8 与并发(二)

    一、并行流与并行排序 Java 8中可以在接口不变的情况下,将流改为并行流,方便在多线程中进行集合中的数据处理。 ...

  • java 并行处理

    参考链接 http://blog.dyngr.com/blog/2016/09/15/java-forkjoinp...

  • Bug 篇:Java 8 Parallel Stream 陷阱

    误区一:Java 8 中的 Stream 只要使用 parallel 就可以并行处理,只要使用 sequentia...

  • java面试题(三)

    Java 多线程模块 1、并行和并发有什么区别? 并行:多个处理器或多核处理器同时处理多个任务。 并发:多个任务在...

  • Java8 ParallelStream流

    前言 并行编程势不可挡,Java从1.7开始就提供了Fork/Join 支持并行处理。java1.8 进一步加强。...

  • 并行流和串行流

    并行流 并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。 Java8中将并行进行了优化,...

  • Java8并发编程(使用CompletableFuture)

    java8异步处理 非阻塞IO/异步/并行 使用场景 scala事件驱动 Java CompletableFutu...

  • Stream

    并行流 并行流就是把一个内容分成多个数据库,并用不同的线程分别处理每个数据块的流 Java8中将并行流进行了优化,...

  • Java 8 并行流(Parallel Stream) 介绍

    这篇博客,我们将介绍Java中的并行流(Parallel Stream)。 Java 8引入了并行流的概念来进行并...

网友评论

      本文标题:Java 中的并行处理

      本文链接:https://www.haomeiwen.com/subject/ichspltx.html