• Java8并行流使用注意事项


    对于从事Java开发的童鞋来说,相信对于Java8的并行流并不陌生,没错,我们常常用它来执行并行任务,但是由于并行流(parallel stream)采用的是享线程池,可能会对我们的性能造成严重影响,那怎么处理呢?

    问题

    首先我们来看看具体的问题。在开发中,我们常常通过以下方法,实现并行流执行并行任务:

    myList.parallelStream.map(obj -> longRunningOperation())

    但是这存在一个严重的问题:在 JVM 的后台,使用通用的 fork/join 池来完成上述功能,该池是所有并行流共享的。默认情况,fork/join 池会为每个处理器分配一个线程。假设你有一台16核的机器,这样你就只能创建16个线程。对 CPU 密集型的任务来说,这样是有意义的,因为你的机器确实只能执行16个线程。但是真实情况下,不是所有的任务都是 CPU 密集型的。例如:

    myList.parallelStream 

      .map(this::retrieveFromA)

      .map(this::processUsingB)

      .forEach(this::saveToC)

    myList.parallelStream 

      .map(this::retrieveFromD)

      .map(this::processUsingE)

      .forEach(this::saveToD)

    这两个流很大程度上是受限于IO操作,所以会等待其他系统。但这两个流使用相同的(小)线程池,因此会相互等待而被阻塞,非常不友好。比如:

    final List<Integer> firstRange = buildIntRange(); 

       firstRange.parallelStream().forEach((number) -> {

          try {

             // do something slow

             Thread.sleep(5);

          } catch (InterruptedException e) { }

    });

    在执行期间,我获取了一份线程dump的文件。这是相关的线程:

    ForkJoinPool.commonPool-worker-1 

    ForkJoinPool.commonPool-worker-2 

    ForkJoinPool.commonPool-worker-3 

    ForkJoinPool.commonPool-worker-4

    现在,我要并行的执行这两个并行流:

    Runnable firstTask = () -> { 

      firstRange.parallelStream().forEach((number) -> {

        try {

          // do something slow

          Thread.sleep(5);

        } catch (InterruptedException e) { }

      });

    };

    Runnable secondTask = () -> { 

      secondRange.parallelStream().forEach((number) -> {

        try {

          // do something slow

          Thread.sleep(5);

        } catch (InterruptedException e) { }

      });

    };

    // run threads

    这次我们再看一下线程dump文件:

    ForkJoinPool.commonPool-worker-1 

    ForkJoinPool.commonPool-worker-2 

    ForkJoinPool.commonPool-worker-3 

    ForkJoinPool.commonPool-worker-4

    正如你所见,结果是一样的。我们只使用了4个线程。

    解决办法

    对于上面的问题,我们可以在JVM 后台使用 fork/join 池,在 ForkJoinTask 的文档中,我们可以看到:

    如果合适,安排一个异步执行的任务到当前正在运行的池中。如果任务不在inForkJoinPool()中,也可以调用ForkJoinPool.commonPool()获取新的池来执行,比如:

    ForkJoinPool forkJoinPool = new ForkJoinPool(3); 

    forkJoinPool.submit(() -> { 

      firstRange.parallelStream().forEach((number) -> {

        try {

          Thread.sleep(5);

        } catch (InterruptedException e) { }

      });

    });

    ForkJoinPool forkJoinPool2 = new ForkJoinPool(3); 

    forkJoinPool2.submit(() -> { 

      secondRange.parallelStream().forEach((number) -> {

        try {

          Thread.sleep(5);

        } catch (InterruptedException e) {

        }

      });

    });

    现在,我们再次查看线程池:

    ForkJoinPool-1-worker-1 

    ForkJoinPool-1-worker-2 

    ForkJoinPool-1-worker-3 

    ForkJoinPool-1-worker-4 

    ForkJoinPool-2-worker-1 

    ForkJoinPool-2-worker-2 

    ForkJoinPool-2-worker-3 

    ForkJoinPool-1-worker-4

    上面这种方法为什么又正确显示了呢?因为我们创建自己的线程池,所以可以避免共享线程池,如果有需要,甚至可以分配比处理机数量更多的线程。

    ForkJoinPool forkJoinPool = new ForkJoinPool(<numThreads>);

    以上就是Java8并行流在使用中所存在的一些问题及解决办法,部分内容参考自一个Java教学网站,希望对Java初学者有所帮助。

  • 相关阅读:
    zabbix4.4安装和简要设置
    SAMBA服务
    NFS服务
    Rsync+inotify数据同步
    Linux上FTP部署:基于mariadb管理虚拟用户
    rsyslog日志服务部署
    Typora自动生成标题编号
    编译安装LAMP
    303. 区域和检索
    [leetcode]53. 最大子序和*
  • 原文地址:https://www.cnblogs.com/gaobig/p/4874400.html
Copyright © 2020-2023  润新知