Java并行流:一次搞定多线程编程难题,让你的程序飞起来!

2023-11-06

前言

  在日常的工作中,为了提高程序的处理速度,充分利用多核处理器的性能,我们需要手动编写多线程代码。但是多线程编程非常复杂,容易出现死锁、竞态条件等问题,给我们带来了很大的困扰。而 Java 并行流则提供了一种更加简单、易用、安全的并发编程方式,可以让我们更加轻松地编写高效的并发程序。

使用多线程下载文件

public class MultiThreadExample {

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

        List<String> urls = Arrays.asList(
            "https://example.com/file1.txt",
            "https://example.com/file2.txt",
            "https://example.com/file3.txt",
            "https://example.com/file4.txt",
            "https://example.com/file5.txt"
        );
        
        int threads = 5;
        int chunkSize = urls.size() / threads;
        int startIndex = 0;
        int endIndex = chunkSize;

        // 创建线程列表
        List<DownloadThread> downloadThreads = new ArrayList<>();

        // 启动多个线程进行文件下载
        for (int i = 0; i < threads; i++) {
            downloadThreads.add(new DownloadThread(urls, startIndex, endIndex));
            downloadThreads.get(i).start();
            startIndex += chunkSize;
            endIndex += chunkSize;
        }

        // 等待所有线程结束并汇总结果
        for (DownloadThread downloadThread : downloadThreads) {
            downloadThread.join();
        }

        System.out.println("文件下载完成");
    }
}

class DownloadThread extends Thread {
    private List<String> urls;
    private int start;
    private int end;

    public DownloadThread(List<String> urls, int start, int end) {
        this.urls = urls;
        this.start = start;
        this.end = end;
    }

    @Override
    public void run() {
        for (int i = start; i < end; i++) {
            HttpUtil.download(urls.get(i));
        }
    }
}
复制代码

  我们首先将要下载的文件 URL 存储在一个 List 中,然后为每个块创建了一个 DownloadThread 对象,并启动了多个线程进行下载操作。每个线程只负责处理 URL 的一个块,调用 HttpUtil.download 方法进行文件下载操作。最后,我们等待所有线程结束即可。

使用Fork/Join进行下载

  import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

public class ForkJoinExample {

    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/file1.txt", 
            "https://example.com/file2.txt", 
            "https://example.com/file3.txt", 
            "https://example.com/file4.txt", 
            "https://example.com/file5.txt"
        );
        ForkJoinPool pool = new ForkJoinPool();
        pool.invoke(new DownloadAction(urls, 0, urls.size()));
        System.out.println("文件下载完成");
    }

    static class DownloadAction extends RecursiveAction {
        private List<String> urls;
        private int start;
        private int end;

        public DownloadAction(List<String> urls, int start, int end) {
            this.urls = urls;
            this.start = start;
            this.end = end;
        }

        @Override
        protected void compute() {
            if (end - start <= 1) {
                HttpUtil.download(urls.get(start));
                return;
            }

            int mid = (start + end) / 2;
            DownloadAction leftAction = new DownloadAction(urls, start, mid);
            DownloadAction rightAction = new DownloadAction(urls, mid, end);
            invokeAll(leftAction, rightAction);
        }
    }
}
复制代码

  在这个示例中,我们使用了 ForkJoin 框架来实现文件下载。首先,我们创建了一个 DownloadAction 类,继承自 RecursiveAction 类,表示一个递归操作。在 compute 方法中,我们首先判断当前操作的 URL 是否为一个,如果是,则直接调用 HttpUtil.download 方法进行文件下载。如果不是,则将 URL 列表分为两半,分别创建两个子任务进行处理,然后使用 invokeAll 方法将这两个子任务提交到线程池中并等待它们完成。

  在 main 方法中,我们首先创建了一个 ForkJoinPool 对象,然后调用 invoke 方法来执行 DownloadAction 操作。在这里,我们使用了默认的线程池,也可以根据需要创建自定义的线程池。

使用Java并行流

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

public class ParallelStreamExample {

    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/file1.txt", 
            "https://example.com/file2.txt", 
            "https://example.com/file3.txt", 
            "https://example.com/file4.txt", 
            "https://example.com/file5.txt"
        );
        urls.parallelStream().forEach(url -> HttpUtil.download(url));
        System.out.println("文件下载完成");
    }
}
复制代码

  在这个示例中,我们使用了 Java 并行流来实现文件下载。首先,我们创建了一个 URL 列表,然后使用 parallelStream 方法将其转换为并行流。接着,我们使用 forEach 方法遍历并行流中的每个 URL,并使用 HttpUtil.download 方法进行文件下载。在这个过程中,Java 会自动将并行流中的元素分配给多个线程并行执行,以提高程序的性能。

Java 并行流是什么?

  好了,相信看了上面的案例,应该对并行流有了一个简单的认识了吧。让原本又丑又长的代码,一下就变得眉清目秀了。所以那让我们进一步的来了解它吧。

  Java 并行流是 Java 8 中新增的一个特性,它提供了一种便捷的方式来进行并发计算。在传统的 Java 编程中,为了利用多核处理器的性能,我们需要手动编写多线程代码。但是多线程编程非常复杂,容易出现死锁、竞态条件等问题,给我们带来了很大的困扰。而 Java 并行流则提供了一种更加简单、易用、安全的并发编程方式,可以让我们更加轻松地编写高效的并发程序。

  Java 并行流的核心是将数据集合分成多个小块,然后在多个处理器上并行处理,最后将结果合并成一个结果集。使用 Java 并行流可以有效地利用多核处理器的性能,提升程序运行效率。此外,Java 并行流还提供了一系列的中间操作和终止操作,可以方便地进行数据筛选、映射、过滤等操作。

Java并行流的实现原理?

  Java 并行流是基于 Fork/Join 框架实现的,它使用了多线程来处理流操作。具体来说,Java 并行流的实现原理如下:

  1. 拆分数据

  当并行流操作开始时,数据会被拆分成多个小块。每个小块都会被分配给不同的线程去处理。

  1. 执行任务

  每个线程会独立地执行任务。线程会使用 fork/join 框架将自己的任务拆分成更小的子任务,并将这些子任务分配给其他线程。

  1. 合并结果

  当所有线程完成任务后,它们会将自己的结果合并到一起。这个过程类似于 reduce 操作,不同之处在于它是并行的。

  Java 并行流的是基于 Fork/Join 框架实现的,而Fork/Join 框架是 Java 7 引入的一个用于并行计算的框架,它基于工作窃取算法,可以将一个大任务拆分成多个小任务,每个线程独立地处理一个小任务。在 Java 8 中,通过对 Stream 接口的扩展,使得并行计算更加容易实现。

  需要注意的是,Java 并行流在执行操作时,会根据当前计算机的 CPU 核心数来确定并行线程的数量,如果并行线程数量过多,会造成过多的上下文切换,反而会降低程序的性能。因此,在使用并行流时需要注意控制并行线程的数量。

三种方式对比

   在文件下载这个例子中,我们使用了多线程、ForkJoin 框架和 Java 并行流三种方式来实现。我们来对比一下这三种方式的优缺点。

1. 多线程方式

优点:

  • 可以手动控制线程的数量,适用于对线程数量有特殊要求的场景。
  • 可以使用线程池来重用线程,减少线程创建和销毁的开销。
  • 可以使用 waitnotify 等机制来实现线程间的通信和协作。

缺点:

  • 需要手动编写线程的创建和销毁代码,代码复杂度较高。
  • 线程之间的协作和通信需要手动实现,容易出现死锁等问题。
  • 代码的可读性和可维护性较差。

2. ForkJoin 框架方式

优点:

  • 可以自动地将任务拆分成更小的子任务,并将子任务分配给多个线程并行执行,简化了代码实现。
  • 可以通过调整并行度来优化性能,提高代码的灵活性。
  • 可以使用默认的线程池或自定义的线程池来管理线程。

缺点:

  • 不适用于 IO 密集型操作,仅适用于 CPU 密集型操作。
  • 线程之间的协作和通信需要手动实现,容易出现死锁等问题。

3. Java 并行流方式

优点:

  • 可以使用函数式编程的方式简化代码实现,代码可读性较高。
  • 可以自动地将数据分配给多个线程并行处理,简化了代码实现。
  • 可以根据需要选择并行度来优化性能。
  • 可以通过流水线方式优化代码性能,提高代码的灵活性。

缺点:

  • 不适用于 IO 密集型操作,仅适用于 CPU 密集型操作。
  • 对于一些特殊的操作,例如排序和去重,可能需要手动调整代码才能使用并行流。

总结

  Java并行流可以让多线程编程变得更加简单易懂,减少编程中的并发问题,提高代码质量和可维护性。帮助开发人员更加轻松地实现任务并行,充分利用多核处理器的性能,加快程序的执行速度。但是虽然并行流有诸多优点,但是还需要根据具体场景来选择合适的方式。如果是 IO 密集型操作,我们应该使用多线程或者 Java NIO 等技术来实现;如果是 CPU 密集型操作,我们可以使用 ForkJoin 框架或者 Java 并行流来实现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java并行流:一次搞定多线程编程难题,让你的程序飞起来! 的相关文章

随机推荐

  • 2023年无人航空系统与航空航天国际会议(ICUASA 2023)

    2023年无人航空系统与航空航天国际会议 ICUASA 2023 重要信息 会议网址 www icuasa org 会议时间 2023年2月18 20日 召开地点 中国广州 截稿时间 2023年12月30日 录用通知 投稿后2周内 收录检索
  • numpy、pandas实用总结(3种数据合并)

    前言 将俩个或者多个DataFrame合并在一起 这样的操作在日常工作中是极为频繁的一件事情 目前 我所知的有四种将DataFrame合并在一起 的方法 concat 在Series中也可以使用 merge join concat合并 这种
  • hdu 1438 钥匙计数之一

    Problem acm hdu edu cn showproblem php pid 1438 Reference blog csdn net u010405898 article details 9530769 blog csdn net
  • 线程池+枚举+反射调用不同接口获得统一返回数值

    首先接口函数的定义 使用策略模式 不同的接口实现类统一实现一个被实现的接口类 public interface ThreadServiceBase 用来被继承使用 然后不同的接口实现类都实现这个接口 每个实现类有自己的定义业务接口 例如 接
  • c++命名空间

    介绍C 命名空间的一篇文章 刚刚学C 的时候 我被满篇的域作用符号 都搞晕了 这篇文章终于让我熟悉了C 的命名空间 为什么需要命名空间 如何使用命名空间 当然 觉得java的import机制更加优雅 至少没有满篇的域作用符 一 为什么需要命
  • Java中Static关键字的使用

    1 Static关键字的用途 1 1 Static修饰成员变量 1 1 1 Static修饰成员变量的需求 当我们在代码中出现一个成员变量在我们每次创建不同的对象时 所赋值都一样 我们就可以把它放在一个公共区域内 不管几个对象都可以操作使用
  • Arduino+sim800C家居安防火灾报警 拨打电话 发送短信例程程序

    家居安防报警器 参考程序 火灾报警 涉及用sim800c发短信 拨打电话通知 接线 Sim800c 3 3V gt Arduino 3 3V Sim800c GND gt Arduino GND Sim800c RX gt Arduino
  • BES2300x笔记(24) -- 如何进行软件加密保护

    哈喽大家好 这是该系列博文的第二十四篇 篇 lt lt 系列博文索引 快速通道 gt gt 以下内容基于原厂提供的文档整理润色 一 前言 由于当下知识产权意识的普遍淡薄 当我们开发一款产品 推向市场之后 如何能防止第三方破解代码 便成了维护
  • 【解决问题】idea的右上方工具栏不见了的解决办法

    前一天使用idea还正常的 第二天看idea的时候 idea右上方的工具栏不见了 截图如下 那么如何让她显示出来了呢 方案1 目标 显示在右上方 在3的前面勾选下 打钩的话就行 打钩后结果 方案2 目标 显示在左上方 在3出打钩 效果如下
  • git 清除所有untracked file

    上次合并分支的时候 出现了一些没见过的文件 有 orig等等 如下图 接下来 就是git的神奇操作命令 git clean f 将所有untracked file 一次性删除 就大功告成了
  • upf低功耗的一个简单的例子

    一 结构描述 这里是一个uart top模块里面例化了两个uart 分别给两个模块给了power switch 用于电源控制 对这两个模块分别进行隔离关断来写一个简单的upf 二 代码分析 1 电压域 create power domain
  • python闭包

    python语言中形成闭包的三个条件 缺一不可 1 必须有一个内嵌函数 函数里定义的函数 这对应函数之间的嵌套 例如下面在函数funx 里面又定义了一个funy 函数 2 内嵌函数必须引用一个定义在闭合范围内 外部函数里 的变量 内部函数引
  • [ERR] Node 192.168.1.77:7000 is not empty. Either the node already knows other nodes (check with CLU

    ERR Node 192 168 1 77 7000 is not empty Either the node already knows other nodes check with CLUSTER NODES or contains s
  • AWS大数据三种经典玩法

    目录 前言 1 AWS一站式大数据分析平台 2 让人不得不喜欢的AWS 优点 缺点 3 个人建议 4 文章说明 一 AWS大数据平台全家桶简介 1 一首 数据源 2 一尾 数据应用 3 中间 大数据体系 3 1导入组件 3 2数据处理组件
  • 我跟面试官说MySQL单表数据量不要超过两千万,面试官不信

    导读 作为一个合格的 DBA 在遇到线上单表数据量超过千万级别的时候 往往会建议用户通过分表来缩减单表数据量 当用户问为什么单表数据量不能超过千万时 DBA 往往会说 单表数据量超过千万 会影响查询性能 知其然而不知所以然 学习技术不能停留
  • rtsp采用MD5加密摘要认证的交互过程

    RTSP使用MD5加密认证代码实现 链接 https edu csdn net learn 38258 606141 spm 1003 2001 3001 4157 RTSP实时音视频传输介绍 文章中详细介绍了RTSP实时音视频传输的过程
  • 车企为何要造手机?

    去年这个时候 有一个很火的词叫 互联网造车 当时百度 小米 阿里 滴滴等互联网公司纷纷宣布造车 而今年一开年 业界又出来一个热词 叫 车企造手机 吉利宣布要投入100亿来造手机 计划于2023年推出产品 最近又传出 吉利正在收购魅族手机 蔚
  • Microsoft Dynamics CRM Server 2011安装配置

    基于公司需要 要在服务器 Server 2008 R2 上安装CRM 老板想要安装Microsoft Dynamics CRM Server 2011 于是就去微软官网上下载了试用版进行安装调试 Microsoft Dynamics CRM
  • Python: 实现评分算法(附完整源代码)

    Python 实现评分算法 附完整源代码 评分是衡量产品或服务质量的指标之一 在许多领域中 评分都是非常重要的 例如电影 饭店 应用程序等 为了将产品或服务分类以便进行比较 我们需要使用评分算法 在本篇文章中 我们将介绍如何使用Python
  • Java并行流:一次搞定多线程编程难题,让你的程序飞起来!

    前言 在日常的工作中 为了提高程序的处理速度 充分利用多核处理器的性能 我们需要手动编写多线程代码 但是多线程编程非常复杂 容易出现死锁 竞态条件等问题 给我们带来了很大的困扰 而 Java 并行流则提供了一种更加简单 易用 安全的并发编程