前言
在项目中,为了实现“并发编程”(同时执行多个任务),就引入了“多进程编程”,把一个很大的任务,拆分成若干个很小的任务,创建多个进程,每个进程分别负责其中的一部分任务。
这也带来了一些问题:创建/销毁进程,比较低效,所以就引入了线程,每个线程都是一个独立的执行流,一个进程包含了一个或多个线程,创建线程/销毁线程 比 创建进程/销毁进程 更高效。
因此,在 Java 圈子中,大部分的并发编程都是通过多线程的方式实现的,但是进程也有线程比不上的优势 ----> 进程的独立性
操作系统中,同一时刻运行着很多个进程,如果某一个进程挂了,不会影响其他的进程(每个进程有各自的地址空间),相比之下,由于多个线程共用一个进程的地址空间,某个线程挂了,就有可能会把整个进程都祸害掉
所以对于使用进程还是线程实现并发编程,我们要根据项目的特性来考虑
多进程介绍
站在操作系统的角度(如 Linux )提供了很多和多进程编程相关的接口,比如:进程创建,进程终止,进程等待,进程程序替换,进程间通信 ......
而 Java 中对系统提供的这些操作进行了限制,最终给用户提供了两个操作:1.进程创建 2.进程等待
进程创建&等待
创建出一个新的进程,让这个新的进程来执行一系列的任务,被创建出来的进程称为子进程,创建子进程的进程称为父进程
代码演示
/**
* 测试进程的创建
* */
public class TestExec {
public static void main(String[] args) throws IOException, InterruptedException {
// Runtime 采用了单例模式,只能有一个实例
Runtime runtime=Runtime.getRuntime();
//创建并运行了一个进程,相当于在控制台输入了 javac 运行
Process process=runtime.exec("javac");
// javac 是一个控制台程序,它的输出是输出到“标准输出”和“标准错误”这两个特殊文件中的
//要想看到这个程序的运行结果,就要去获取“标准输出”和“标准错误”这两个特殊文件中的内容
//为什么在 idea 中看不到上述进程运行的结果?
//一个进程在启动的时候会自动的打开 3 个文件:
// 1.标准输入(对应键盘)
// 2.标准输出(对应显示器)
// 3.标准错误(对应显示器)
//虽然子进程启动以后也打开了这 3 个文件,但是由于子进程没有和 idea 的终端关联,因此在 idea 中看不到子进程的输出,需要手动获取
// 获取标准输出
InputStream stdOutFrom=process.getInputStream();
FileOutputStream stdOutTo=new FileOutputStream("stdOut.txt");
while (true){
int ch=stdOutFrom.read();
if(ch==-1){
break;
}
stdOutTo.write(ch);
}
stdOutFrom.close();
stdOutTo.close();
//获取标准错误
InputStream stdErrFrom=process.getErrorStream();
FileOutputStream stdErrTo=new FileOutputStream("stdError.txt");
while (true){
int ch=stdErrFrom.read();
if(ch==-1){
break;
}
stdErrTo.write(ch);
}
stdErrFrom.close();
stdErrTo.close();
//执行 Process 类的 waitFor 方法来实现进程的等待
//父进程执行到 waitFor 方法时就会阻塞,阻塞到子进程执行完毕为止(和 Thread.join 是很像的)
//返回的 exitCode 是退出码,退出码用于表示进程是否正确执行完毕,如果正确执行完毕就返回 0 ,非 0 代表进程出现异常
int exitCode=process.waitFor();
System.out.println(exitCode);
}
}
执行以上代码,可以得到空的 stdOut.txt 文件,与如下所示的 stdError.txt 文件,说明我们成功创建了一个进程,执行了 javac 命令
注意,以上的演示代码相当于创建了一个进程执行了 javac 操作,相当于在控制器中输入了 javac 并执行,要是没有配置 jdk 的环境变量就得不了正确的结果,关于环境变量的配置推荐看
配置 JDK 环境变量(最简单)