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 命令,通过输入输出流,将子进程的 标准输出 和 错误输出 写到对应文件中. 

    public static void main(String[] args) throws IOException, InterruptedException {
        //Runtime 再 JVM 中是一个单例
        Runtime runtime = Runtime.getRuntime();
        //Process 就表示进程
        Process process = runtime.exec("javac");

        //获取子进程的标准输出和标准错误,并写道两个文件中
        //1.标准hou出
        //1) 通过 标准输入流 将子进程的标准输出读出来,写入到 stdout.txt 文件中
        InputStream stdoutFrom = process.getInputStream();
        FileOutputStream stdoutTo = new FileOutputStream("stdout.txt");
        while(true) {
            int ch = stdoutFrom.read();
            if(ch == -1) { //读到 EOF 为止(EOF 就是 -1)
                break;
            }
            stdoutTo.write(ch);
        }
        //2) 释放文件描述符
        stdoutFrom.close();
        stdoutTo.close();

        //2.标准错误
        //2) 通过标准输入流 将子进程的标准错误读出来,写入到 stderr.txt
        InputStream stderrFrom = process.getErrorStream();
        FileOutputStream stderrTo = new FileOutputStream("stderr.txt");
        while(true) {
            int ch = stderrFrom.read();
            if(ch == -1) {
                break;
            }
            stderrTo.write(ch);
        }
        //2) 释放文件描述符
        stderrFrom.close();
        stderrTo.close();

    }

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

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

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

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

1.2.3、进程等待

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

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

具体实现如下:

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

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

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

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

public class CommandUtil {

    /**
     * @param cmd 进程要执行的命令
     * @param stdout 标准输出文件名 + 后缀
     * @param stderr 标准错误文件名 + 后缀
     * @return 进程等待后返回的状态码
     */
    public static int run(String cmd, String stdout, String stderr) {
        try {
            //1.获取 Runtime 实例,执行 exec 方法
            Runtime runtime = Runtime.getRuntime();
            Process process = runtime.exec(cmd);
            //2.获取 标准输出
            if(stdout != null) {
                InputStream stdoutFrom = process.getInputStream();
                FileOutputStream stdoutTo = new FileOutputStream(stdout);
                while(true) {
                    int read = stdoutFrom.read();
                    if(read == -1) {
                        break;
                    }
                    stdoutTo.write(read);
                }
                stdoutFrom.close();
                stdoutTo.close();
            }
            //3.获取 标准错误
            if(stderr != null) {
                InputStream stderrFrom = process.getErrorStream();
                FileOutputStream stderrTo = new FileOutputStream(stderr);
                while(true) {
                    int read = stderrFrom.read();
                    if(read == -1) {
                        break;
                    }
                    stderrTo.write(read);
                }
                stderrFrom.close();
                stderrTo.close();
            }
            //4.进程等待
            return process.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //返回异常的状态码
        return 1;
    }

    //测试
    public static void main(String[] args) {
        int result = run("javac", "stdout.txt", "stderr.txt");
        System.out.println(result);
    }

}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈亦康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值