• Java - 多进程编程(对比线程、API 操作)


    目录

    一、多进程编程

    1.1、为什么要使用多进程编程

    1.2、Java 中多进程编程的实现

    1.2.1、前言

    1.2.2、进程创建

    1.2.3、进程等待

    1.2.4、封装操作到一个工具类中


    一、多进程编程


    1.1、为什么要使用多进程编程

    一个 .exe 文件执行以后,就会变成一个进程. 

    多进程的由来:为了解决某些大型复杂问题,就需要把一个很大的任务,拆分成一个小的任务,进一步的,就需要使用 “多进程编程”,也就是床技安多个进程,每个进程分别负责其中一部分任务.  与此同时,也带来一个问题——“进程的 创建/销毁,比较重量(低效)”.

    线程的由来:引入了线程,相比于 进程的 创建/销毁 更加高效,因此 Java 圈子中,大部分并发编程都是通过多线程的方式来实现.

    什么情况下要使用多进程编程呢?

    进程相比于线程最大的优势就是:进程的 “独立性”.

    • 多线程劣势:一个操作系统上,同一时刻一个进程中运行着多个线程(共用一个地址空间),某个线程挂了,就可能把整个线程带走;
    • 多进程优势:一个操作系统上,同一时刻运行着很多进程,由于不同的进程有各自的地址空间,那么即使某一个进程挂了,也不会影响到其他进程.

    比如在一个 OJ 系统中,用户提交的代码就是一个独立的逻辑,整个逻辑就需要使用多进程的方式来执行.  因为用户的代码很有可能是存在问题的(一运行就崩溃),使用多线程就很有可能导致用户代码直接把整个服务器进程搞挂.

    1.2、Java 中多进程编程的实现

    1.2.1、前言

    在操作系统的角度上(例如 Linux),提供了很多和多线程编程相关的接口,例如 进程创建、进程终止、进程等待、进程程序替换、进程间通讯.

    但是在 Java 中对这些操作进行了限制,最终只提供了两个操作:进程创建 和 进程等待.

    1.2.2、进程创建

    通过 Runtime 实例中的 exec 方法(参数是一个字符串,相当于在 cmd 中输入了一个对应的指令)就可以创建出一个进程, 被创建出来的进程称为 “子进程”,创建子进程的进程称为 “父进程”.  咱们的服务器进程就相当于父进程,它可以有多个子进程,但是一个子进程只能有一个父进程.

    一个进程在启动的时候,就会自动以下打开三个文件:

    1. 标准输入:对应到键盘.
    2. 标准输出:对应到显示器,用来正确的输出.
    3. 标准错误:对应到显示器,用来展示错误输出.

    Ps:在 IDEA 中式看不到子进程的输出的,想要获取,可以手动获取.

    例如,创建一个子进程运行 javac 命令,通过输入输出流,将子进程的 标准输出 和 错误输出 写到对应文件中. 

    1. public static void main(String[] args) throws IOException, InterruptedException {
    2. //Runtime 再 JVM 中是一个单例
    3. Runtime runtime = Runtime.getRuntime();
    4. //Process 就表示进程
    5. Process process = runtime.exec("javac");
    6. //获取子进程的标准输出和标准错误,并写道两个文件中
    7. //1.标准hou出
    8. //1) 通过 标准输入流 将子进程的标准输出读出来,写入到 stdout.txt 文件中
    9. InputStream stdoutFrom = process.getInputStream();
    10. FileOutputStream stdoutTo = new FileOutputStream("stdout.txt");
    11. while(true) {
    12. int ch = stdoutFrom.read();
    13. if(ch == -1) { //读到 EOF 为止(EOF 就是 -1)
    14. break;
    15. }
    16. stdoutTo.write(ch);
    17. }
    18. //2) 释放文件描述符
    19. stdoutFrom.close();
    20. stdoutTo.close();
    21. //2.标准错误
    22. //2) 通过标准输入流 将子进程的标准错误读出来,写入到 stderr.txt
    23. InputStream stderrFrom = process.getErrorStream();
    24. FileOutputStream stderrTo = new FileOutputStream("stderr.txt");
    25. while(true) {
    26. int ch = stderrFrom.read();
    27. if(ch == -1) {
    28. break;
    29. }
    30. stderrTo.write(ch);
    31. }
    32. //2) 释放文件描述符
    33. stderrFrom.close();
    34. stderrTo.close();
    35. }

    运行后可以看到生成如下两个文件:

    由于这里我只是单纯的输入 javac 命令(没有指定编译的 jar 包),因此是一个错误命令,那么就可以在 标准错误 中看到如下信息:

    Ps:javac 往控制台输出的命令,再 windows 简体中文版系统中,默认是 gbk 编码,idea 默认式 utf8,打开后可能会乱码,因此只需要再 idea 提示中,通过 gbk 重新加载即可.

    在 cmd 中输入 javac 也是一样的结果:

    1.2.3、进程等待

    在某些场景中,我们希望父进程等待子进程执行完毕以后,再执行后续的代码. 

    例如,OJ 系统就需要让用户提交代码,然后编译执行代码,再把执行结果的响应返回给用户.

    具体实现如下:

    通过 Process 类中的 waitFor 方法实现进程等待,父进程执行到 waitFor 的时候就会阻塞,知道子进程执行完毕为止(类似 Thread.join). 最后会返回一个退出码,表示子进程执行结果是否 ok,正常退出就返回 0,异常退出(子进程执行过程中抛异常了)就非0.

    1. //进程等待
    2. int exitCode = process.waitFor();
    3. System.out.println(exitCode);

    1.2.4、封装操作到一个工具类中

    通常,我们会将进程创建和等待封装到一个工具类中去使用.

    1. public class CommandUtil {
    2. /**
    3. * @param cmd 进程要执行的命令
    4. * @param stdout 标准输出文件名 + 后缀
    5. * @param stderr 标准错误文件名 + 后缀
    6. * @return 进程等待后返回的状态码
    7. */
    8. public static int run(String cmd, String stdout, String stderr) {
    9. try {
    10. //1.获取 Runtime 实例,执行 exec 方法
    11. Runtime runtime = Runtime.getRuntime();
    12. Process process = runtime.exec(cmd);
    13. //2.获取 标准输出
    14. if(stdout != null) {
    15. InputStream stdoutFrom = process.getInputStream();
    16. FileOutputStream stdoutTo = new FileOutputStream(stdout);
    17. while(true) {
    18. int read = stdoutFrom.read();
    19. if(read == -1) {
    20. break;
    21. }
    22. stdoutTo.write(read);
    23. }
    24. stdoutFrom.close();
    25. stdoutTo.close();
    26. }
    27. //3.获取 标准错误
    28. if(stderr != null) {
    29. InputStream stderrFrom = process.getErrorStream();
    30. FileOutputStream stderrTo = new FileOutputStream(stderr);
    31. while(true) {
    32. int read = stderrFrom.read();
    33. if(read == -1) {
    34. break;
    35. }
    36. stderrTo.write(read);
    37. }
    38. stderrFrom.close();
    39. stderrTo.close();
    40. }
    41. //4.进程等待
    42. return process.waitFor();
    43. } catch (Exception e) {
    44. e.printStackTrace();
    45. }
    46. //返回异常的状态码
    47. return 1;
    48. }
    49. //测试
    50. public static void main(String[] args) {
    51. int result = run("javac", "stdout.txt", "stderr.txt");
    52. System.out.println(result);
    53. }
    54. }

  • 相关阅读:
    谷粒商城--品牌管理(OSS、JSR303数据校验)
    Typora + EasyBlogImageForTypora直接上传图片到博客园
    【定义】三角形行列式和对角行列式
    【驯服野生verilog-mode全记录】day1 —— 常用链接与基本命令模板
    【ONE·Linux || 多线程(一)】
    社区动态——恭喜海豚调度中国区用户组新晋 9 枚“社群管理员”
    大数据存储基石——HDFS
    如果你项目使用了MyBatis-Plus你一定要用它
    环境安装与准备
    工程管理系统简介 工程管理系统源码 java工程管理系统 工程管理系统功能设计
  • 原文地址:https://blog.csdn.net/CYK_byte/article/details/133960722