目录
一个 .exe 文件执行以后,就会变成一个进程.
多进程的由来:为了解决某些大型复杂问题,就需要把一个很大的任务,拆分成一个小的任务,进一步的,就需要使用 “多进程编程”,也就是床技安多个进程,每个进程分别负责其中一部分任务. 与此同时,也带来一个问题——“进程的 创建/销毁,比较重量(低效)”.
线程的由来:引入了线程,相比于 进程的 创建/销毁 更加高效,因此 Java 圈子中,大部分并发编程都是通过多线程的方式来实现.
什么情况下要使用多进程编程呢?
进程相比于线程最大的优势就是:进程的 “独立性”.
比如在一个 OJ 系统中,用户提交的代码就是一个独立的逻辑,整个逻辑就需要使用多进程的方式来执行. 因为用户的代码很有可能是存在问题的(一运行就崩溃),使用多线程就很有可能导致用户代码直接把整个服务器进程搞挂.
在操作系统的角度上(例如 Linux),提供了很多和多线程编程相关的接口,例如 进程创建、进程终止、进程等待、进程程序替换、进程间通讯.
但是在 Java 中对这些操作进行了限制,最终只提供了两个操作:进程创建 和 进程等待.
通过 Runtime 实例中的 exec 方法(参数是一个字符串,相当于在 cmd 中输入了一个对应的指令)就可以创建出一个进程, 被创建出来的进程称为 “子进程”,创建子进程的进程称为 “父进程”. 咱们的服务器进程就相当于父进程,它可以有多个子进程,但是一个子进程只能有一个父进程.
一个进程在启动的时候,就会自动以下打开三个文件:
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 也是一样的结果:

在某些场景中,我们希望父进程等待子进程执行完毕以后,再执行后续的代码.
例如,OJ 系统就需要让用户提交代码,然后编译执行代码,再把执行结果的响应返回给用户.
具体实现如下:
通过 Process 类中的 waitFor 方法实现进程等待,父进程执行到 waitFor 的时候就会阻塞,知道子进程执行完毕为止(类似 Thread.join). 最后会返回一个退出码,表示子进程执行结果是否 ok,正常退出就返回 0,异常退出(子进程执行过程中抛异常了)就非0.
- //进程等待
- int exitCode = process.waitFor();
- System.out.println(exitCode);
通常,我们会将进程创建和等待封装到一个工具类中去使用.
- 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);
- }
-
- }
